Repository: megaease/easegress
Pull request #170 (2021-08-05 11:19:51)

add unit tests

codecov-commenter: # [Codecov](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease) Report
> Merging [#170](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease) (3efc907) into [main](https://codecov.io/gh/megaease/easegress/commit/1c3ca11395d8afe9ca83769abca7f9a445e26448?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease) (1c3ca11) will **increase** coverage by `1.35%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/megaease/easegress/pull/170/graphs/tree.svg?width=650&height=150&src=pr&token=5Q80B98LPI&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease)](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease)

```diff
@@            Coverage Diff             @@
##             main     #170      +/-   ##
==========================================
+ Coverage   72.68%   74.03%   +1.35%     
==========================================
  Files          49       49              
  Lines        5323     5323              
==========================================
+ Hits         3869     3941      +72     
+ Misses       1227     1149      -78     
- Partials      227      233       +6     
```


| [Impacted Files](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease) | Coverage Δ | |
|---|---|---|
| [pkg/cluster/member.go](https://codecov.io/gh/megaease/easegress/pull/170/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease#diff-cGtnL2NsdXN0ZXIvbWVtYmVyLmdv) | `67.91% <0.00%> (+2.61%)` | :arrow_up: |
| [pkg/cluster/op.go](https://codecov.io/gh/megaease/easegress/pull/170/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease#diff-cGtnL2NsdXN0ZXIvb3AuZ28=) | `64.22% <0.00%> (+9.17%)` | :arrow_up: |
| [pkg/cluster/watcher.go](https://codecov.io/gh/megaease/easegress/pull/170/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease#diff-cGtnL2NsdXN0ZXIvd2F0Y2hlci5nbw==) | `64.93% <0.00%> (+16.88%)` | :arrow_up: |
| [pkg/cluster/syncer.go](https://codecov.io/gh/megaease/easegress/pull/170/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease#diff-cGtnL2NsdXN0ZXIvc3luY2VyLmdv) | `73.71% <0.00%> (+18.58%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease). Last update [1c3ca11...3efc907](https://codecov.io/gh/megaease/easegress/pull/170?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=megaease).


Patch:
diff --git a/go.mod b/go.mod
index ca1512cf..99e5a1ff 100644
--- a/go.mod
+++ b/go.mod
@@ -11,8 +11,7 @@ require (
 	github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9 // indirect
 	github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
 	github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
-	github.com/fatih/color v1.12.0
-	github.com/fatih/structs v1.1.0
+	github.com/fatih/color v1.9.0
 	github.com/ghodss/yaml v1.0.0
 	github.com/go-chi/chi/v5 v5.0.3
 	github.com/go-zookeeper/zk v1.0.2
diff --git a/go.sum b/go.sum
index f77ebb55..a10c455d 100644
--- a/go.sum
+++ b/go.sum
@@ -432,11 +432,8 @@ github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpm
 github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
 github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239/go.mod h1:Gdwt2ce0yfBxPvZrHkprdPPTTS3N5rwmLE8T22KBXlw=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
-github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
-github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
-github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
-github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
 github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c=
@@ -885,9 +882,8 @@ github.com/marten-seemann/qtls-go1-17 v0.1.0-beta.1.2 h1:SficYjyOthSrliKI+EaFuXS
 github.com/marten-seemann/qtls-go1-17 v0.1.0-beta.1.2/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
 github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
-github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
-github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
diff --git a/pkg/cluster/member.go b/pkg/cluster/member.go
index 6a24e3d3..bf5de6f7 100644
--- a/pkg/cluster/member.go
+++ b/pkg/cluster/member.go
@@ -197,19 +197,6 @@ func (m *members) _selfWithoutID() *member {
 	return s
 }
 
-func (m *members) isSelfIDChanged() bool {
-	return m.selfIDChanged
-}
-
-func (m *members) clusterMember() *membersSlice {
-	m.RLock()
-	defer m.RUnlock()
-
-	copied := m.ClusterMembers.copy()
-
-	return &copied
-}
-
 func (m *members) clusterMembersLen() int {
 	m.RLock()
 	defer m.RUnlock()
@@ -246,18 +233,6 @@ func (m *members) knownMembersLen() int {
 	return m.KnownMembers.Len()
 }
 
-// NOTE: Maybe use it in future in case of connecting
-// one member purged but running at another etcd cluster.
-func (m *members) deleteKnownMember(name string) {
-	m.Lock()
-	defer m.Unlock()
-
-	// NOTE: It's fine to delete myself,
-	// because it will restore myself in newMembers while restarting.
-	m.KnownMembers.deleteByName(name)
-	m.store()
-}
-
 func (m *members) knownPeerURLs() []string {
 	m.RLock()
 	defer m.RUnlock()
diff --git a/pkg/common/callback.go b/pkg/common/callback.go
deleted file mode 100644
index d09116ff..00000000
--- a/pkg/common/callback.go
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-const (
-	// CallbacksInitCapacity is the capacity of callback functions.
-	CallbacksInitCapacity = 20
-
-	// NormalPriorityCallback is the name of normal priority callback.
-	NormalPriorityCallback = "__NoRmAl_PrIoRiTy_CaLlBaCk"
-	// CriticalPriorityCallback is the name of critical priority callback.
-	CriticalPriorityCallback = "__CrItIcAl_PrIoRiTy_CaLlBaCk"
-)
-
-// NamedCallback is the named callback struct
-type NamedCallback struct {
-	name     string
-	callback interface{}
-}
-
-// NewNamedCallback returns a new named callback.
-func NewNamedCallback(name string, callback interface{}) *NamedCallback {
-	return &NamedCallback{
-		name:     name,
-		callback: callback,
-	}
-}
-
-// Name returns callback name
-func (cb *NamedCallback) Name() string {
-	return cb.name
-}
-
-// Callback returns callback function
-func (cb *NamedCallback) Callback() interface{} {
-	return cb.callback
-}
-
-// SetCallback sets the callback function
-func (cb *NamedCallback) SetCallback(callback interface{}) interface{} {
-	oriCallback := cb.callback
-	cb.callback = callback
-	return oriCallback
-}
-
-type namedCallbackWithIdx struct {
-	cb         *NamedCallback
-	idxOfOrder int
-}
-
-// NamedCallbackSet is a set of NamedCallback
-type NamedCallbackSet struct {
-	// critical callback takes low index, normal callback takes high index
-	callbacks []*NamedCallback                 // index for access by order
-	names     map[string]*namedCallbackWithIdx // index for access by callback constant
-
-}
-
-// NewNamedCallbackSet returns a new NamedCallbackSet
-func NewNamedCallbackSet() *NamedCallbackSet {
-	return &NamedCallbackSet{
-		names:     make(map[string]*namedCallbackWithIdx, CallbacksInitCapacity),
-		callbacks: make([]*NamedCallback, 0, CallbacksInitCapacity),
-	}
-}
-
-// CopyCallbacks copies the current callbacks
-func (cbs *NamedCallbackSet) CopyCallbacks() []*NamedCallback {
-	ret := make([]*NamedCallback, len(cbs.callbacks))
-	copy(ret, cbs.callbacks)
-	return ret
-}
-
-// GetCallbacks get callbacks
-func (cbs *NamedCallbackSet) GetCallbacks() []*NamedCallback {
-	return cbs.callbacks
-}
-
-////
-
-// AddCallback adds a callback into a callback set with priority
-func AddCallback(cbs *NamedCallbackSet, name string, callback interface{}, priority string) *NamedCallbackSet {
-	if cbs == nil {
-		return nil
-	}
-
-	_, exists := cbs.names[name]
-	if exists {
-		return cbs
-	}
-
-	cb := NewNamedCallback(name, callback)
-	idx := 0
-
-	if priority == NormalPriorityCallback {
-		idx = len(cbs.callbacks)
-		cbs.callbacks = append(cbs.callbacks, cb)
-	} else if priority == CriticalPriorityCallback {
-		// idx = 0
-		cbs.callbacks = append([]*NamedCallback{cb}, cbs.callbacks...)
-	} else {
-		idx = len(cbs.callbacks)
-		for i, namedCallback := range cbs.callbacks {
-			if namedCallback.Name() == priority {
-				idx = i
-				break
-			}
-		}
-
-		// insert before the pos
-		cbs.callbacks = append(cbs.callbacks[:idx], append([]*NamedCallback{cb}, cbs.callbacks[idx:]...)...)
-	}
-
-	cbs.names[name] = &namedCallbackWithIdx{
-		cb:         cb,
-		idxOfOrder: idx,
-	}
-
-	return cbs
-}
-
-// DeleteCallback deletes a callback in callback set
-func DeleteCallback(cbs *NamedCallbackSet, name string) *NamedCallbackSet {
-	if cbs == nil {
-		return nil
-	}
-
-	cbi, exists := cbs.names[name]
-	if !exists {
-		return cbs
-	}
-
-	delete(cbs.names, name)
-	cbs.callbacks = append(cbs.callbacks[:cbi.idxOfOrder], cbs.callbacks[cbi.idxOfOrder+1:]...)
-
-	for _, cbi1 := range cbs.names {
-		if cbi1.idxOfOrder > cbi.idxOfOrder {
-			cbi1.idxOfOrder--
-		}
-	}
-
-	return cbs
-}
diff --git a/pkg/common/stat_aggregator.go b/pkg/common/stat_aggregator.go
deleted file mode 100644
index c79bfaa8..00000000
--- a/pkg/common/stat_aggregator.go
+++ /dev/null
@@ -1,277 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"fmt"
-	"reflect"
-)
-
-// StatAggregator is the interface
-type StatAggregator interface {
-	Aggregate(interface{}) error
-	Result() interface{}
-	String() string
-}
-
-type numericKind uint
-
-const (
-	invalidNum numericKind = iota
-	intNum
-	uintNum
-	floatNum
-)
-
-// String returns the type of number.
-func (nk numericKind) String() string {
-	switch nk {
-	case invalidNum:
-		return "invalid"
-	case intNum:
-		return "int[8|16|32|64]"
-	case uintNum:
-		return "uint[8|16|32|64]"
-	case floatNum:
-		return "float[32|64]"
-	}
-	return "unknown numericKind"
-}
-
-func unifyNumericKind(num interface{}) (interface{}, numericKind) {
-	switch reflect.ValueOf(num).Kind() {
-	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		return reflect.ValueOf(num).Int(), intNum
-	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		return reflect.ValueOf(num).Uint(), uintNum
-	case reflect.Float32, reflect.Float64:
-		return reflect.ValueOf(num).Float(), floatNum
-	default:
-		return num, invalidNum
-	}
-}
-
-// NumericMaxAggregator is the structure with max value
-type NumericMaxAggregator struct {
-	max interface{}
-	nk  numericKind
-}
-
-// String returns the name.
-func (a *NumericMaxAggregator) String() string {
-	return "numeric_max"
-}
-
-// Aggregate records the max of value
-func (a *NumericMaxAggregator) Aggregate(num interface{}) error {
-	if num == nil {
-		return nil
-	}
-
-	kind := reflect.ValueOf(num).Kind()
-	value, nk := unifyNumericKind(num)
-	if nk == invalidNum {
-		return fmt.Errorf("unsupported kind %s", kind)
-	}
-
-	if a.max == nil {
-		a.max, a.nk = value, nk
-		return nil
-	}
-
-	if a.nk != nk {
-		return fmt.Errorf("want kind %s, got %s", a.nk, kind)
-	}
-
-	switch a.nk {
-	case intNum:
-		if a.max.(int64) < value.(int64) {
-			a.max = value
-		}
-	case uintNum:
-		if a.max.(uint64) < value.(uint64) {
-			a.max = value
-		}
-	case floatNum:
-		if a.max.(float64) < value.(float64) {
-			a.max = value
-		}
-	default:
-		return fmt.Errorf("BUG: unsupported numeric kind %s", a.nk)
-	}
-
-	return nil
-}
-
-// Result return the max value
-func (a *NumericMaxAggregator) Result() interface{} {
-	return a.max
-}
-
-// NumericMinAggregator is the structure with min value
-type NumericMinAggregator struct {
-	min interface{}
-	nk  numericKind
-}
-
-// String returns the name.
-func (a *NumericMinAggregator) String() string {
-	return "numeric_min"
-}
-
-// Aggregate records the min value
-func (a *NumericMinAggregator) Aggregate(num interface{}) error {
-	if num == nil {
-		return nil
-	}
-
-	kind := reflect.ValueOf(num).Kind()
-	value, nk := unifyNumericKind(num)
-	if nk == invalidNum {
-		return fmt.Errorf("unsupported kind %s", kind)
-	}
-
-	if a.min == nil {
-		a.min, a.nk = value, nk
-		return nil
-	}
-
-	if a.nk != nk {
-		return fmt.Errorf("want kind %s, got %s", a.nk, kind)
-	}
-
-	switch a.nk {
-	case intNum:
-		if a.min.(int64) > value.(int64) {
-			a.min = value
-		}
-	case uintNum:
-		if a.min.(uint64) > value.(uint64) {
-			a.min = value
-		}
-	case floatNum:
-		if a.min.(float64) > value.(float64) {
-			a.min = value
-		}
-	default:
-		return fmt.Errorf("BUG: unsupported numeric kind %s", a.nk)
-	}
-
-	return nil
-}
-
-// Result returns the min value.
-func (a *NumericMinAggregator) Result() interface{} {
-	return a.min
-}
-
-// NumericSumAggregator is the structure with sum value
-type NumericSumAggregator struct {
-	sum interface{}
-	nk  numericKind
-}
-
-// String returns the name.
-func (a *NumericSumAggregator) String() string {
-	return "numeric_sum"
-}
-
-// Aggregate records the sum value
-func (a *NumericSumAggregator) Aggregate(num interface{}) error {
-	if num == nil {
-		return nil
-	}
-
-	kind := reflect.ValueOf(num).Kind()
-	value, nk := unifyNumericKind(num)
-	if nk == invalidNum {
-		return fmt.Errorf("unsupported kind %s", kind)
-	}
-
-	if a.sum == nil {
-		a.sum, a.nk = value, nk
-		return nil
-	}
-
-	if a.nk != nk {
-		return fmt.Errorf("want kind %s, got %s", a.nk, kind)
-	}
-
-	switch a.nk {
-	case intNum:
-		a.sum = a.sum.(int64) + value.(int64)
-	case uintNum:
-		a.sum = a.sum.(uint64) + value.(uint64)
-	case floatNum:
-		a.sum = a.sum.(float64) + value.(float64)
-	default:
-		return fmt.Errorf("BUG: unsupported numeric kind %s", a.nk)
-	}
-
-	return nil
-}
-
-// Result returns the sum value.
-func (a *NumericSumAggregator) Result() interface{} {
-	return a.sum
-}
-
-// NumericAvgAggregator is the structure with average value
-type NumericAvgAggregator struct {
-	NumericSumAggregator
-	count int64
-}
-
-// String returns the name.
-func (a *NumericAvgAggregator) String() string {
-	return "numeric_average"
-}
-
-// Aggregate records the number of values and the sum of values
-func (a *NumericAvgAggregator) Aggregate(num interface{}) error {
-	if num == nil {
-		return nil
-	}
-
-	err := a.NumericSumAggregator.Aggregate(num)
-	if err != nil {
-		return err
-	}
-
-	a.count++
-
-	return nil
-}
-
-// Result returns the average values
-func (a *NumericAvgAggregator) Result() interface{} {
-	if a.NumericSumAggregator.Result() == nil {
-		return nil
-	}
-
-	switch a.nk {
-	case intNum:
-		return a.NumericSumAggregator.Result().(int64) / int64(a.count)
-	case uintNum:
-		return a.NumericSumAggregator.Result().(uint64) / uint64(a.count)
-	case floatNum:
-		return a.NumericSumAggregator.Result().(float64) / float64(a.count)
-	default:
-		return nil
-	}
-}
diff --git a/pkg/common/strings.go b/pkg/common/strings.go
deleted file mode 100644
index f5f2fa8b..00000000
--- a/pkg/common/strings.go
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"reflect"
-	"unsafe"
-)
-
-// B2s converts byte slice to a string without memory allocation.
-// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
-//
-// Note it may break if string and/or slice header will change
-// in the future go versions.
-func B2s(b []byte) string {
-	return *(*string)(unsafe.Pointer(&b))
-}
-
-// S2b converts string to a byte slice without memory allocation.
-//
-// Note it may break if string and/or slice header will change
-// in the future go versions.
-func S2b(s string) []byte {
-	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
-	bh := reflect.SliceHeader{
-		Data: sh.Data,
-		Len:  sh.Len,
-		Cap:  sh.Len,
-	}
-	return *(*[]byte)(unsafe.Pointer(&bh))
-}
diff --git a/pkg/common/utils.go b/pkg/common/utils.go
index fb91fb88..a8d78b12 100644
--- a/pkg/common/utils.go
+++ b/pkg/common/utils.go
@@ -18,191 +18,14 @@
 package common
 
 import (
-	"bytes"
 	"fmt"
 	"io"
 	"os"
 	"path/filepath"
 	"regexp"
 	"runtime"
-	"strconv"
-	"strings"
 )
 
-// TokenEscapeChar is the escape char
-const TokenEscapeChar string = `\`
-
-// GraphiteSplit slices s into all substrings separated by sep
-// returns a slice of the substrings without length prefix separated by lensep.
-// The routine does its best to split without error returned.
-func GraphiteSplit(s string, lensep string, sep string) []string {
-	a := make([]string, 0)
-
-	i := 0
-	n := len(s)
-	for i < n {
-		j := strings.Index(s[i:], lensep)
-		if j == -1 {
-			return a
-		}
-		j += i
-
-		length, err := strconv.ParseUint(s[i:j], 10, 16)
-		if err != nil {
-			return a
-		}
-
-		i = j + len(lensep)
-		j = i + int(length)
-		if j > n {
-			return a
-		}
-
-		a = append(a, s[i:j])
-
-		i = j
-		if i < n && s[i:i+len(sep)] != sep {
-			return a
-		}
-
-		i += len(sep)
-	}
-	return a
-}
-
-// TokenVisitor uses for visit token
-type TokenVisitor func(pos int, token string) (care bool, replacement string)
-
-// ScanTokens scans tokens.
-func ScanTokens(str string, removeEscapeChar bool, visitor TokenVisitor) (string, error) {
-	if visitor == nil {
-		visitor = func(_ int, _ string) (bool, string) {
-			return false, ""
-		}
-	}
-
-	in := false
-	ret := bytes.NewBuffer(nil)
-	v := []byte(str)
-
-	escaped := func(pos int) bool {
-		if pos == 0 {
-			return false
-		}
-
-		return string(v[pos-1]) == TokenEscapeChar
-	}
-
-	escaper := func(s string) string {
-		return strings.Replace(
-			strings.Replace(s, TokenEscapeChar+`{`, "{", -1),
-			TokenEscapeChar+`}`, "}", -1)
-	}
-
-	token := bytes.NewBuffer(nil)
-	for i, c := range v {
-		if c == '{' && !escaped(i) {
-			if in {
-				return str, fmt.Errorf("invalid pattern string")
-			}
-
-			in = true
-		} else if c == '}' && !escaped(i) {
-			if !in {
-				return str, fmt.Errorf("invalid pattern string")
-			}
-
-			if len(strings.TrimSpace(token.String())) == 0 {
-				return str, fmt.Errorf("empty token")
-			}
-
-			pos := i - token.Len() - 1
-			token = bytes.NewBufferString(escaper(token.String()))
-
-			care, replacement := visitor(pos, token.String())
-			if care {
-				ret.WriteString(replacement)
-				c = 0
-			} else {
-				ret.WriteString("{")
-				ret.WriteString(token.String())
-			}
-
-			token = bytes.NewBuffer(nil)
-			in = false
-		} else if in {
-			token.WriteByte(c)
-		}
-
-		if !in && c != 0 {
-			ret.WriteByte(c)
-		}
-	}
-
-	if in {
-		return str, fmt.Errorf("invalid pattern string")
-	}
-
-	retStr := ret.String()
-	if removeEscapeChar {
-		retStr = escaper(retStr)
-	}
-
-	return retStr, nil
-}
-
-// PanicToErr try to recover panic and returns if it happened or not.
-func PanicToErr(f func(), err *error) (failed bool) {
-	defer func() {
-		x := recover()
-		if x == nil {
-			failed = false
-			return
-		}
-
-		failed = true
-
-		if err == nil {
-			return
-		}
-
-		switch e := x.(type) {
-		case error:
-			*err = e
-		case string:
-			*err = fmt.Errorf(e)
-		default:
-			*err = fmt.Errorf("%v", x)
-		}
-	}()
-
-	f()
-
-	return
-}
-
-var (
-	// TrueStrings contains all string represents true
-	TrueStrings = []string{"1", "t", "true", "on", "y", "yes"}
-	// FalseStrings contains all string represents false
-	FalseStrings = []string{"0", "f", "false", "off", "n", "no"}
-)
-
-// NextNumberPowerOf2 return the number of power of 2
-// Via: https://stackoverflow.com/a/466242/1705845
-//      https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
-func NextNumberPowerOf2(v uint64) uint64 {
-	v--
-	v |= v >> 1
-	v |= v >> 2
-	v |= v >> 4
-	v |= v >> 8
-	v |= v >> 16
-	v |= v >> 32
-	v++
-	return v
-}
-
 // URLFriendlyCharactersRegex - safe characters for friendly url, rfc3986 section 2.3
 var URLFriendlyCharactersRegex = regexp.MustCompile(`^[A-Za-z0-9\-_\.~]{1,253}$`)
 
diff --git a/pkg/common/uuid.go b/pkg/common/uuid.go
deleted file mode 100644
index 49be20c0..00000000
--- a/pkg/common/uuid.go
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"crypto/rand"
-	"fmt"
-	"io"
-)
-
-// UUID returns an UUID
-func UUID() (string, error) {
-	uuid := make([]byte, 16)
-	n, err := io.ReadFull(rand.Reader, uuid)
-	if n != len(uuid) || err != nil {
-		return "", err
-	}
-
-	uuid[8] = uuid[8]&^0xc0 | 0x80
-	uuid[6] = uuid[6]&^0xf0 | 0x40
-
-	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
-}
diff --git a/pkg/filter/mock/mock.go b/pkg/filter/mock/mock.go
index 4c8ad60e..781e4c2a 100644
--- a/pkg/filter/mock/mock.go
+++ b/pkg/filter/mock/mock.go
@@ -143,7 +143,12 @@ func (m *Mock) handle(ctx context.HTTPContext) (result string) {
 			return
 		}
 
-		if rule.Path == path || strings.HasPrefix(path, rule.PathPrefix) {
+		if rule.Path == path {
+			mock(rule)
+			return
+		}
+
+		if rule.PathPrefix != "" && strings.HasPrefix(path, rule.PathPrefix) {
 			mock(rule)
 			return
 		}
diff --git a/pkg/filter/proxy/pool.go b/pkg/filter/proxy/pool.go
index ce8f5dfc..1df07425 100644
--- a/pkg/filter/proxy/pool.go
+++ b/pkg/filter/proxy/pool.go
@@ -212,7 +212,7 @@ func (p *pool) doRequest(ctx context.HTTPContext, req *request) (*http.Response,
 	span := ctx.Span().NewChildWithStart(spanName, req.startTime())
 	span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.std.Header))
 
-	resp, err := globalClient.Do(req.std)
+	resp, err := fnSendRequest(req.std)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/pkg/filter/proxy/proxy.go b/pkg/filter/proxy/proxy.go
index 3019de11..f2af9cac 100644
--- a/pkg/filter/proxy/proxy.go
+++ b/pkg/filter/proxy/proxy.go
@@ -82,6 +82,10 @@ var globalClient = &http.Client{
 	},
 }
 
+var fnSendRequest = func(r *http.Request) (*http.Response, error) {
+	return globalClient.Do(r)
+}
+
 type (
 	// Proxy is the filter Proxy.
 	Proxy struct {
diff --git a/pkg/util/jmxtool/jmx_controller.go b/pkg/util/jmxtool/jmx_controller.go
deleted file mode 100644
index 8cc89c51..00000000
--- a/pkg/util/jmxtool/jmx_controller.go
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 jmxtool
-
-import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-)
-
-// JMXRequestOperationType for read, write, exec, list, search
-type JMXRequestOperationType string
-
-const (
-	jmxMbeanAttributeRead  JMXRequestOperationType = "read"
-	jmxMbeanAttributeWrite JMXRequestOperationType = "write"
-	jmxMbeanExec           JMXRequestOperationType = "exec"
-	jmxMbeanList           JMXRequestOperationType = "list"
-	jmxMbeanSearch         JMXRequestOperationType = "search"
-)
-
-// JXMInterface describes all of methods of Mbean operation
-type JXMInterface interface {
-	GetMbeanAttribute(mbean string, attribute string, path string) (interface{}, error)
-	SetMbeanAttribute(mbean string, attribute string, path string, value interface{}) (interface{}, error)
-	ExecuteMbeanOperation(mbean string, operation string, arguments []interface{}) (interface{}, error)
-	ListMbean(mbean string) (interface{}, error)
-	SearchMbeans(pattern string) (interface{}, error)
-}
-
-type (
-	// JolokiaResponse is the response structure
-	JolokiaResponse struct {
-		Status    uint32
-		Timestamp uint32
-		Request   map[string]interface{}
-		Value     interface{}
-		Error     string
-	}
-
-	readRequestBody struct {
-		Type      JMXRequestOperationType `json:"type"`
-		Mbean     string                  `json:"mbean"`
-		Attribute string                  `json:"attribute"`
-		Path      string                  `json:"path"`
-	}
-
-	writeRequestBody struct {
-		Type      JMXRequestOperationType `json:"type"`
-		Mbean     string                  `json:"mbean"`
-		Attribute string                  `json:"attribute"`
-		Path      string                  `json:"path"`
-		Value     interface{}             `json:"value"`
-	}
-
-	executeRequestBody struct {
-		Type      JMXRequestOperationType `json:"type"`
-		Mbean     string                  `json:"mbean"`
-		Operation string                  `json:"operation"`
-		Arguments []interface{}           `json:"arguments"`
-	}
-
-	listRequestBody struct {
-		Type  JMXRequestOperationType `json:"type"`
-		Mbean string                  `json:"mbean"`
-	}
-
-	searchRequestBody struct {
-		Type  JMXRequestOperationType `json:"type"`
-		Mbean string                  `json:"mbean"`
-	}
-
-	// JolokiaClient wraps the URL and HTTPClient
-	JolokiaClient struct {
-		URL        string
-		HTTPClient *http.Client
-	}
-)
-
-// NewJolokiaClient creates Jolokia client
-func NewJolokiaClient(host, port, path string) *JolokiaClient {
-	return &JolokiaClient{
-		"http://" + host + ":" + port + "/" + path + "/",
-		&http.Client{},
-	}
-}
-
-func (client *JolokiaClient) handleRequest(requestBody []byte) (*JolokiaResponse, error) {
-	request, err := http.NewRequest(
-		"POST",
-		client.URL,
-		bytes.NewBuffer(requestBody),
-	)
-	if err != nil {
-		return nil, err
-	}
-	request.Header.Set("Content-Type", "application/json")
-	response, err := client.HTTPClient.Do(request)
-	if err != nil {
-		return nil, err
-	}
-	defer response.Body.Close()
-	contents, err := ioutil.ReadAll(response.Body)
-	if err != nil {
-		return nil, err
-	}
-
-	jolokiaResponse := JolokiaResponse{}
-	err = json.Unmarshal(contents, &jolokiaResponse)
-	if err != nil {
-		return nil, err
-	}
-	if jolokiaResponse.Status != 200 {
-		return nil, fmt.Errorf("status: %d %s", jolokiaResponse.Status, jolokiaResponse.Error)
-	}
-
-	return &jolokiaResponse, nil
-}
-
-func (client *JolokiaClient) execute(requestBody interface{}) (interface{}, error) {
-	var err error
-	body, err := json.Marshal(requestBody)
-	if err != nil {
-		return nil, err
-	}
-	resp, err := client.handleRequest(body)
-	if err != nil {
-		return nil, fmt.Errorf("access jmx failed: %v", err)
-	}
-	return resp.Value, err
-}
-
-// GetMbeanAttribute reads Mbean attribute.
-func (client *JolokiaClient) GetMbeanAttribute(mbean string, attribute string, path string) (interface{}, error) {
-	requestBody := readRequestBody{
-		Mbean:     mbean,
-		Attribute: attribute,
-		Type:      jmxMbeanAttributeRead,
-	}
-	if path != "" {
-		requestBody.Path = path
-	}
-
-	result, err := client.execute(requestBody)
-	if err != nil {
-		return nil, fmt.Errorf("read Mbean Attribute failed: %v", err)
-	}
-	return result, err
-}
-
-// SetMbeanAttribute writes Mbean attribute
-func (client *JolokiaClient) SetMbeanAttribute(mbean string, attribute string, path string, value interface{}) (interface{}, error) {
-	requestBody := writeRequestBody{
-		Mbean:     mbean,
-		Attribute: attribute,
-		Type:      jmxMbeanAttributeWrite,
-		Value:     value,
-	}
-	if path != "" {
-		requestBody.Path = path
-	}
-
-	result, err := client.execute(requestBody)
-	if err != nil {
-		return nil, fmt.Errorf("write Mbean Attribute failed: %v", err)
-	}
-	return result, nil
-}
-
-// ExecuteMbeanOperation executes Mbean operation
-func (client *JolokiaClient) ExecuteMbeanOperation(mbean string, operation string, arguments []interface{}) (interface{}, error) {
-	requestBody := executeRequestBody{
-		Mbean:     mbean,
-		Operation: operation,
-		Type:      jmxMbeanExec,
-		Arguments: arguments,
-	}
-
-	result, err := client.execute(requestBody)
-	if err != nil {
-		return nil, fmt.Errorf("execute Mbean Operation failed: %v", err)
-	}
-	return result, nil
-}
-
-// ListMbean lists the Mbean
-func (client *JolokiaClient) ListMbean(mbean string) (interface{}, error) {
-	requestBody := listRequestBody{
-		Type:  jmxMbeanList,
-		Mbean: mbean,
-	}
-
-	result, err := client.execute(requestBody)
-	if err != nil {
-		return nil, fmt.Errorf("list Mbean failed: %v", err)
-	}
-	return result, nil
-}
-
-// SearchMbeans searches Mbean
-func (client *JolokiaClient) SearchMbeans(pattern string) (interface{}, error) {
-	requestBody := searchRequestBody{
-		Type:  jmxMbeanSearch,
-		Mbean: pattern,
-	}
-
-	result, err := client.execute(requestBody)
-	if err != nil {
-		return nil, fmt.Errorf("search Mbean failed: %v", err)
-	}
-	return result, nil
-}


Test patch:
diff --git a/pkg/cluster/cluster_test.go b/pkg/cluster/cluster_test.go
index 1e330b41..4ecfbc32 100644
--- a/pkg/cluster/cluster_test.go
+++ b/pkg/cluster/cluster_test.go
@@ -24,10 +24,12 @@ import (
 	"time"
 
 	"go.etcd.io/etcd/api/v3/mvccpb"
+	clientv3 "go.etcd.io/etcd/client/v3"
 )
 
 func mockClusters(count int) []*cluster {
 	opts, _, _ := mockMembers(count)
+
 	clusters := make([]*cluster, count)
 
 	bootCluster, err := New(opts[0])
@@ -113,6 +115,40 @@ func TestLeaseInvalid(t *testing.T) {
 	}
 }
 
+func TestClusterStart(t *testing.T) {
+	opts, _, _ := mockMembers(1)
+
+	cls, err := New(opts[0])
+
+	if err != nil {
+		t.Errorf("init failed: %v", err)
+	}
+
+	c := cls.(*cluster)
+
+	_, _, err = c.StartServer()
+
+	if err != nil {
+		t.Errorf("start server failed, %v", err)
+	}
+}
+
+func TestClusterPurgeMember(t *testing.T) {
+	opts, _, _ := mockMembers(2)
+
+	cls, err := New(opts[0])
+
+	if err != nil {
+		t.Errorf("init failed: %v", err)
+	}
+
+	c := cls.(*cluster)
+	err = c.PurgeMember("no-member")
+	if err == nil {
+		t.Errorf("purge a none exit member, should be failed")
+	}
+}
+
 func TestClusterSyncer(t *testing.T) {
 	opts, _, _ := mockMembers(1)
 	cls, err := New(opts[0])
@@ -134,22 +170,70 @@ func TestClusterSyncer(t *testing.T) {
 		t.Errorf("new syncer failed: %v", err)
 	}
 
-	if _, err = syncer.Sync("akey"); err != nil {
+	schan, err := syncer.Sync("/akey")
+	if err != nil {
 		t.Errorf("syncer sync failed: %v", err)
 	}
 
-	if _, err = syncer.SyncRaw("akey"); err != nil {
+	rschan, err := syncer.SyncRaw("/akey")
+	if err != nil {
 		t.Errorf("syncer sync failed: %v", err)
 	}
 
-	if _, err = syncer.SyncPrefix("akey"); err != nil {
+	c.Put("/akey", "avalue")
+
+	for {
+		var value *string
+		select {
+		case value = <-schan:
+			fmt.Printf("sync value is %v\n", value)
+		}
+		break
+	}
+
+	for {
+		var value *mvccpb.KeyValue
+		select {
+		case value = <-rschan:
+			fmt.Printf("sync raw value is %v\n", value)
+		}
+		break
+	}
+
+	pchan, err := syncer.SyncPrefix("/abcd")
+	if err != nil {
 		t.Errorf("syncer sync failed: %v", err)
 	}
 
-	if _, err = syncer.SyncRawPrefix("akey"); err != nil {
+	rpchan, err := syncer.SyncRawPrefix("/abcd")
+	if err != nil {
 		t.Errorf("syncer sync failed: %v", err)
 	}
 
+	c.Put("/abcd/efg", "yoyo")
+
+	if _, err = c.GetPrefix("/abcd"); err != nil {
+		t.Errorf("cluster get prefix failed: %v", err)
+	}
+
+	for {
+		value := make(map[string]string, 1)
+		select {
+		case value = <-pchan:
+			fmt.Printf("sync prefix value is %v\n", value)
+		}
+		break
+	}
+
+	for {
+		value := make(map[string]*mvccpb.KeyValue)
+		select {
+		case value = <-rpchan:
+			fmt.Printf("sync raw pvalue is %v\n", value)
+		}
+		break
+	}
+
 	syncer.Close()
 
 	wg := &sync.WaitGroup{}
@@ -179,22 +263,89 @@ func TestClusterWatcher(t *testing.T) {
 		t.Errorf("new syncer failed: %v", err)
 	}
 
-	if _, err = watcher.Watch("akey"); err != nil {
+	wchan, err := watcher.Watch("/akey/value")
+	if err != nil {
 		t.Errorf("watcher watch failed: %v", err)
 	}
 
-	if _, err = watcher.WatchRaw("akey"); err != nil {
+	rchan, err := watcher.WatchRaw("/akey/value")
+	if err != nil {
 		t.Errorf("watcher watch failed: %v", err)
 	}
 
-	if _, err = watcher.WatchPrefix("akey"); err != nil {
+	c.Put("/akey/value", "yes")
+
+	for {
+		var value *string
+		select {
+		case value = <-wchan:
+			fmt.Printf("watch value is %v\n", value)
+		}
+		break
+	}
+
+	for {
+		var value *clientv3.Event
+		select {
+		case value = <-rchan:
+			fmt.Printf("watch raw value is %v\n", value)
+		}
+		break
+	}
+
+	pchan, err := watcher.WatchPrefix("/ab")
+	if err != nil {
 		t.Errorf("watcher watch failed: %v", err)
 	}
 
-	if _, err = watcher.WatchRawPrefix("akey"); err != nil {
+	rawpchan, err := watcher.WatchRawPrefix("/abcd")
+	if err != nil {
+
 		t.Errorf("watcher watch failed: %v", err)
 	}
 
+	c.Put("/abc", "kkk")
+
+	for {
+		value := make(map[string]*string, 1)
+		select {
+		case value = <-pchan:
+			fmt.Printf("watch prefix value is %v\n", value)
+		}
+		break
+	}
+	c.Put("/abcd/ef", "jjj")
+
+	for {
+		value := make(map[string]*clientv3.Event, 1)
+		select {
+		case value = <-rawpchan:
+			fmt.Printf("watch prefix raw value is %v\n", value)
+		}
+		break
+	}
+	c.DeletePrefix("/abc")
+
+	for {
+		value := make(map[string]*string, 1)
+		select {
+		case value = <-pchan:
+			fmt.Printf("watch delete prefix value is %v\n", value)
+		}
+		break
+	}
+
+	c.DeletePrefix("/abcd/")
+
+	for {
+		value := make(map[string]*clientv3.Event, 1)
+		select {
+		case value = <-rawpchan:
+			fmt.Printf("watch delete prefix raw value is %v\n", value)
+		}
+		break
+	}
+
 	watcher.Close()
 
 	wg := &sync.WaitGroup{}
@@ -274,14 +425,14 @@ func TestMutexAndOP(t *testing.T) {
 	}
 
 	err = c.PutAndDelete(map[string]*string{
-		"akey": &value,
+		"/test/akey": &value,
 	})
 
 	if err != nil {
 		t.Errorf("PutAndDelete failed :%v", err)
 	}
 
-	if err = c.Delete("akey"); err != nil {
+	if err = c.Delete("/test/akey"); err != nil {
 		t.Errorf("Delete failed: %v", err)
 	}
 
@@ -290,3 +441,36 @@ func TestMutexAndOP(t *testing.T) {
 		t.Errorf("DeletePrefix failed: %v", err)
 	}
 }
+
+func TestUtilEqual(t *testing.T) {
+	equal := isKeyValueEqual(&mvccpb.KeyValue{
+		Key: []byte("abc"),
+	}, &mvccpb.KeyValue{
+		Key: []byte("abc"),
+	})
+
+	if !equal {
+		t.Error("isKeyValueEqual invalid")
+	}
+
+	equal = isKeyValueEqual(nil, &mvccpb.KeyValue{
+		Key: []byte("abc"),
+	})
+
+	if equal {
+		t.Error("isKeyValueEqual invalid, should not equal")
+	}
+
+	equal = isKeyValueEqual(&mvccpb.KeyValue{
+		Key: []byte("abc"),
+	}, nil)
+
+	if equal {
+		t.Error("isKeyValueEqual invalid, should not equal")
+	}
+
+	equal = isKeyValueEqual(nil, nil)
+	if !equal {
+		t.Error("isKeyValueEqual invalid, should equal")
+	}
+}
diff --git a/pkg/cluster/member_test.go b/pkg/cluster/member_test.go
index 520edeac..177d7eab 100644
--- a/pkg/cluster/member_test.go
+++ b/pkg/cluster/member_test.go
@@ -118,6 +118,18 @@ func mockMembers(count int) ([]*option.Options, membersSlice, []*pb.Member) {
 
 	sort.Sort(members)
 
+	tmp := members.copy()
+	if len(tmp) == 0 {
+		panic("members copy failed")
+	}
+	noexistMember := members.getByPeerURL("no-exist")
+
+	if noexistMember != nil {
+		panic("get a member not exist succ, should failed")
+	}
+
+	members.deleteByName("no-exist")
+	members.deleteByPeerURL("no-exist-purl")
 	return opts, members, pbMembers
 }
 
diff --git a/pkg/common/callback_test.go b/pkg/common/callback_test.go
deleted file mode 100644
index 40625a0f..00000000
--- a/pkg/common/callback_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"fmt"
-	"testing"
-)
-
-func createFn(name string) func() string {
-	return func() string {
-		fmt.Println("I am a callback - " + name)
-		return name
-	}
-}
-func TestCallBack(t *testing.T) {
-	foo := createFn("foo")
-	bar := createFn("bar")
-	name := "test"
-	cb := NewNamedCallback(name, foo)
-	if cb.Name() != name {
-		t.Errorf("expected %s, result %s", name, cb.Name())
-	}
-
-	type FuncType func() string
-	cbfn := cb.Callback()
-	if f, ok := cbfn.(func() string); ok {
-		if foo() != FuncType(f)() {
-			t.Errorf("error")
-		}
-	}
-
-	cb.SetCallback(bar)
-	cbfn = cb.Callback()
-	if f, ok := cbfn.(func() string); ok {
-		if bar() != FuncType(f)() {
-			t.Errorf("error")
-		}
-	}
-}
-
-func TestCallBackSet(t *testing.T) {
-	set := NewNamedCallbackSet()
-
-	x := createFn("XXX")
-	y := createFn("YYY")
-	z := createFn("ZZZ")
-	a := createFn("AAA")
-	b := createFn("BBB")
-
-	AddCallback(set, "XXX", x, NormalPriorityCallback)
-	AddCallback(set, "YYY", y, NormalPriorityCallback)
-	AddCallback(set, "ZZZ", z, CriticalPriorityCallback)
-	AddCallback(set, "AAA", a, "ZZZ")
-	AddCallback(set, "BBB", b, "AAA")
-
-	type FuncType func() string
-	for _, cb := range set.CopyCallbacks() {
-		if f, ok := cb.callback.(func() string); ok {
-			if cb.name != FuncType(f)() {
-				t.Errorf("expected %s, result: %s", cb.name, FuncType(f)())
-			} else {
-				DeleteCallback(set, cb.name)
-			}
-
-		}
-	}
-
-	if DeleteCallback(nil, "") != nil {
-		t.Errorf("delete the nil set should not be OK")
-	}
-	DeleteCallback(set, "ABC")
-
-	n := len(set.GetCallbacks())
-	if n > 0 {
-		t.Errorf("expected: 0, result:%d", n)
-	}
-}
diff --git a/pkg/common/stat_aggregator_test.go b/pkg/common/stat_aggregator_test.go
deleted file mode 100644
index bac48dee..00000000
--- a/pkg/common/stat_aggregator_test.go
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"fmt"
-	"reflect"
-	"testing"
-)
-
-type errMapper struct {
-	errs  []error
-	index int
-}
-
-func newErrMapper(errs []error) *errMapper { return &errMapper{errs: errs} }
-
-func (em *errMapper) mapNext(t *testing.T, err error) {
-	if em.index >= len(em.errs) {
-		t.Fatalf("BUG: map beyond existent entries")
-	}
-
-	var (
-		want = "<nil>"
-		got  = "<nil>"
-	)
-
-	switch {
-	case em.errs[em.index] == nil && err == nil:
-		em.index++
-		return
-	case em.errs[em.index] != nil && err == nil:
-		want = em.errs[em.index].Error()
-	case em.errs[em.index] == nil && err != nil:
-		got = err.Error()
-	case em.errs[em.index] != nil && err != nil:
-		want = em.errs[em.index].Error()
-		got = err.Error()
-		if want == got {
-			em.index++
-			return
-		}
-	}
-
-	t.Fatalf("map failed in [%d]:\nwant: %s\ngot : %s", em.index, want, got)
-}
-
-func mockUnsupportedKindError(got interface{}) error {
-	return fmt.Errorf("unsupported kind %s", reflect.ValueOf(got).Kind())
-}
-
-func mockWantXGotYKindError(want numericKind, got interface{}) error {
-	return fmt.Errorf("want kind %s, got %s",
-		want,
-		reflect.ValueOf(got).Kind())
-}
-
-func TestNumericMaxAggregatorInt(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(""),
-		nil,
-		nil,
-		mockWantXGotYKindError(intNum, uint(0)),
-		nil,
-		nil,
-		mockWantXGotYKindError(intNum, float64(0.0)),
-	})
-
-	a := &NumericMaxAggregator{}
-	em.mapNext(t, a.Aggregate(""))
-	em.mapNext(t, a.Aggregate(int(2)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(uint(10)))
-	em.mapNext(t, a.Aggregate(int(10)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(float64(2.0)))
-
-	wantResult := int64(10)
-	gotResult := a.Result().(int64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-	name := "numeric_max"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericMaxAggregatorUint(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(""),
-		nil,
-		nil,
-	})
-
-	a := &NumericMaxAggregator{}
-	em.mapNext(t, a.Aggregate(""))
-	em.mapNext(t, a.Aggregate(uint(2)))
-	em.mapNext(t, a.Aggregate(uint(3)))
-
-	wantResult := uint64(3)
-	gotResult := a.Result().(uint64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-	name := "numeric_max"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericMaxAggregatorFloat(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(""),
-		nil,
-		nil,
-		mockWantXGotYKindError(floatNum, uint(0)),
-	})
-
-	a := &NumericMaxAggregator{}
-	em.mapNext(t, a.Aggregate(""))
-	em.mapNext(t, a.Aggregate(float64(100)))
-	em.mapNext(t, a.Aggregate(float64(-2)))
-	em.mapNext(t, a.Aggregate(uint(10)))
-
-	wantResult := float64(100)
-	gotResult := a.Result().(float64)
-	if gotResult != wantResult {
-		t.Fatalf("want %+v, got %+v", wantResult, gotResult)
-	}
-	name := "numeric_max"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericMinAggregatorFloat(t *testing.T) {
-	em := newErrMapper([]error{
-		nil,
-		mockWantXGotYKindError(floatNum, int(-2)),
-		mockWantXGotYKindError(floatNum, uint(10)),
-		nil,
-		nil,
-		mockUnsupportedKindError([]float64{-2.3}),
-	})
-
-	a := &NumericMinAggregator{}
-	em.mapNext(t, a.Aggregate(float64(2.3)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(uint(10)))
-	em.mapNext(t, a.Aggregate(float32(-100)))
-	em.mapNext(t, a.Aggregate(float64(100)))
-	em.mapNext(t, a.Aggregate([]float64{-2.3}))
-
-	wantResult := float64(-100)
-	gotResult := a.Result().(float64)
-	if gotResult != wantResult {
-		t.Fatalf("want %v, got %v", wantResult, gotResult)
-	}
-
-	name := "numeric_min"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericMinAggregatorInt(t *testing.T) {
-	em := newErrMapper([]error{
-		nil,
-		mockWantXGotYKindError(intNum, uint(3)),
-		mockWantXGotYKindError(intNum, uint(10)),
-		nil,
-	})
-
-	a := &NumericMinAggregator{}
-	em.mapNext(t, a.Aggregate(int(23)))
-	em.mapNext(t, a.Aggregate(uint(3)))
-	em.mapNext(t, a.Aggregate(uint(10)))
-	em.mapNext(t, a.Aggregate(int(-100)))
-
-	wantResult := int64(-100)
-	gotResult := a.Result().(int64)
-	if gotResult != wantResult {
-		t.Fatalf("want %v, got %v", wantResult, gotResult)
-	}
-
-	name := "numeric_min"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericMinAggregatorUint(t *testing.T) {
-	em := newErrMapper([]error{
-		nil,
-		mockWantXGotYKindError(uintNum, int(3)),
-		mockWantXGotYKindError(uintNum, int(10)),
-		nil,
-	})
-
-	a := &NumericMinAggregator{}
-	em.mapNext(t, a.Aggregate(uint(23)))
-	em.mapNext(t, a.Aggregate(int(3)))
-	em.mapNext(t, a.Aggregate(int(10)))
-	em.mapNext(t, a.Aggregate(uint(25)))
-
-	wantResult := uint64(23)
-	gotResult := a.Result().(uint64)
-	if gotResult != wantResult {
-		t.Fatalf("want %v, got %v", wantResult, gotResult)
-	}
-
-	name := "numeric_min"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericSumAggregatorUint(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(map[int]int{0: 1}),
-		nil,
-		mockWantXGotYKindError(uintNum, int(-2)),
-		nil,
-		mockWantXGotYKindError(uintNum, float32(10.1)),
-		nil,
-	})
-
-	a := &NumericSumAggregator{}
-	em.mapNext(t, a.Aggregate(map[int]int{0: 1}))
-	em.mapNext(t, a.Aggregate(uint(2)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(uint(10)))
-	em.mapNext(t, a.Aggregate(float32(10.1)))
-	em.mapNext(t, a.Aggregate(uint(0)))
-
-	wantResult := uint64(12)
-	gotResult := a.Result().(uint64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-
-	name := "numeric_sum"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericSumAggregatorInt(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(map[int]int{0: 1}),
-		nil,
-		mockWantXGotYKindError(intNum, uint(2)),
-		nil,
-		mockWantXGotYKindError(intNum, float32(10.1)),
-	})
-
-	a := &NumericSumAggregator{}
-	em.mapNext(t, a.Aggregate(map[int]int{0: 1}))
-	em.mapNext(t, a.Aggregate(int(2)))
-	em.mapNext(t, a.Aggregate(uint(2)))
-	em.mapNext(t, a.Aggregate(int(10)))
-	em.mapNext(t, a.Aggregate(float32(10.1)))
-
-	wantResult := int64(12)
-	gotResult := a.Result().(int64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-
-	name := "numeric_sum"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericSumAggregatorFloat(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(map[int]int{0: 1}),
-		nil,
-		mockWantXGotYKindError(floatNum, int(-2)),
-		nil,
-	})
-
-	a := &NumericSumAggregator{}
-	em.mapNext(t, a.Aggregate(map[int]int{0: 1}))
-	em.mapNext(t, a.Aggregate(float64(2)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(float64(10)))
-
-	wantResult := float64(12)
-	gotResult := a.Result().(float64)
-	if gotResult != wantResult {
-		t.Fatalf("want %+v, got %+v", wantResult, gotResult)
-	}
-
-	name := "numeric_sum"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericAvgAggregatorInt(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(make(chan int)),
-		nil,
-		nil,
-		mockWantXGotYKindError(intNum, float32(-10.0)),
-		mockWantXGotYKindError(intNum, uint(100)),
-		nil,
-		nil,
-		nil,
-	})
-
-	a := &NumericAvgAggregator{}
-	em.mapNext(t, a.Aggregate(make(chan int)))
-	em.mapNext(t, a.Aggregate(int(2)))
-	em.mapNext(t, a.Aggregate(int(-2)))
-	em.mapNext(t, a.Aggregate(float32(-10.0)))
-	em.mapNext(t, a.Aggregate(uint(100)))
-	em.mapNext(t, a.Aggregate(int(-10)))
-	em.mapNext(t, a.Aggregate(int(0)))
-	em.mapNext(t, a.Aggregate(int(110)))
-
-	wantResult := int64(20)
-	gotResult := a.Result().(int64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-
-	name := "numeric_average"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericAvgAggregatorFloat(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(make(chan int)),
-		nil,
-		nil,
-	})
-
-	a := &NumericAvgAggregator{}
-	em.mapNext(t, a.Aggregate(make(chan int)))
-	em.mapNext(t, a.Aggregate(float64(2)))
-	em.mapNext(t, a.Aggregate(float64(-2)))
-
-	wantResult := float64(0)
-	gotResult := a.Result().(float64)
-	if gotResult != wantResult {
-		t.Fatalf("want %+v, got %+v", wantResult, gotResult)
-	}
-
-	name := "numeric_average"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
-
-func TestNumericAvgAggregatorUint(t *testing.T) {
-	em := newErrMapper([]error{
-		mockUnsupportedKindError(make(chan int)),
-		nil,
-		nil,
-		mockWantXGotYKindError(uintNum, float32(-10.0)),
-		mockWantXGotYKindError(uintNum, int(100)),
-		nil,
-		nil,
-		nil,
-	})
-
-	a := &NumericAvgAggregator{}
-	em.mapNext(t, a.Aggregate(make(chan int)))
-	em.mapNext(t, a.Aggregate(uint(2)))
-	em.mapNext(t, a.Aggregate(uint(4)))
-	em.mapNext(t, a.Aggregate(float32(-10.0)))
-	em.mapNext(t, a.Aggregate(int(100)))
-	em.mapNext(t, a.Aggregate(uint(4)))
-	em.mapNext(t, a.Aggregate(uint(2)))
-
-	wantResult := uint64(3)
-	gotResult := a.Result().(uint64)
-	if gotResult != wantResult {
-		t.Fatalf("want %d, got %d", wantResult, gotResult)
-	}
-
-	name := "numeric_average"
-	if a.String() != name {
-		t.Errorf("want %s, got %s", "numeric_max", name)
-	}
-}
diff --git a/pkg/common/strings_test.go b/pkg/common/strings_test.go
deleted file mode 100644
index 920ca03d..00000000
--- a/pkg/common/strings_test.go
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 common
-
-import (
-	"testing"
-)
-
-func TestStrings(t *testing.T) {
-	str1 := "Hello, World"
-	b := S2b(str1)
-	str2 := B2s(b)
-	if str1 != str2 {
-		t.Errorf("expected %s, result %s", str1, str2)
-	}
-
-	b = []byte{72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100}
-	s := B2s(b)
-	if s != str1 {
-		t.Errorf("expected %s, result %s", str1, s)
-	}
-
-}
diff --git a/pkg/common/utils_test.go b/pkg/common/utils_test.go
index bb65a1ae..3ed6eb75 100644
--- a/pkg/common/utils_test.go
+++ b/pkg/common/utils_test.go
@@ -18,350 +18,14 @@
 package common
 
 import (
+	"crypto/rand"
 	"fmt"
-	"math"
+	"io"
+	"path/filepath"
+	"runtime"
 	"testing"
 )
 
-func TestScanTokensNormally(t *testing.T) {
-	ret, err := ScanTokens(`abcdef`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abcdef` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi`, false, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\{def\}ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{def}ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	visitor := func(pos int, token string) (bool, string) {
-		if pos != 13 {
-			t.Fatalf("wrong token position: %d", pos)
-		}
-		if token != `jkl` {
-			t.Fatalf("wrong token: %s", token)
-		}
-
-		return true, `JKL`
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi{jkl}`, true, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{def}ghiJKL` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi{jkl}`, false, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\{def\}ghiJKL` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi{jkl}lmn`, true, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{def}ghiJKLlmn` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi{jkl}\{lmn\}`, false, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\{def\}ghiJKL\{lmn\}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	// escape char out of token
-
-	ret, err = ScanTokens(`abc\{def`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def`, false, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\{def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\{ghi`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{def{ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\}def`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc}def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\}def`, false, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\}def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\}def\}ghi\{`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc}def}ghi{` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	// escape char in token
-
-	ret, err = ScanTokens(`abc\{defghi{\{jkl\{}`, true, nil)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{defghi{{jkl{}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	visitor = func(pos int, token string) (bool, string) {
-		if pos != 11 {
-			t.Fatalf("wrong token position: %d", pos)
-		}
-		if token != `{jkl{` {
-			t.Fatalf("wrong token: %s", token)
-		}
-
-		return true, `\{JKL\}`
-	}
-
-	ret, err = ScanTokens(`abc\{defghi{\{jkl\{}`, true, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc{defghi{JKL}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{defghi{\{jkl\{}`, false, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `abc\{defghi\{JKL\}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	visitor = func(_ int, token string) (bool, string) {
-		if token != `hello` {
-			t.Fatalf("wrong token: %s", token)
-		}
-
-		return true, `world`
-	}
-
-	ret, err = ScanTokens(`\{hello\} - {hello}`, true, visitor)
-	if err != nil {
-		t.Fatalf("unexpected error")
-	}
-	if ret != `{hello} - world` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-}
-
-func TestScanTokensExceptionally(t *testing.T) {
-	ret, err := ScanTokens(`abc{def`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc{def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc}def`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc}def` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def{ghi`, false, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc\{def{ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def}ghi`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc\{def}ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc}def}ghi`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc}def}ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc{def{ghi`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc{def{ghi` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\}def\{ghi{`, false, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc\}def\{ghi{` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}ghi}`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc\{def\}ghi}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-
-	ret, err = ScanTokens(`abc\{def\}{\{ghi\{{jkl}}`, true, nil)
-	if err == nil {
-		t.Fatalf("expected error unraied")
-	}
-	if ret != `abc\{def\}{\{ghi\{{jkl}}` {
-		t.Fatalf("scan token returns wrong result: %s", ret)
-	}
-}
-
-func TestNextNumberPowerOf2(t *testing.T) {
-	ret := NextNumberPowerOf2(0)
-	if ret != 0 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(1)
-	if ret != 1 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(2)
-	if ret != 2 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(8)
-	if ret != 8 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(1023)
-	if ret != 1024 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(math.MaxUint32)
-	if ret != math.MaxUint32+1 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(math.MaxUint32 + 1)
-	if ret != math.MaxUint32+1 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(1<<63 - 1)
-	if ret != 1<<63 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-
-	ret = NextNumberPowerOf2(1 << 63)
-	if ret != 1<<63 {
-		t.Fatalf("unexpected return: %d", ret)
-	}
-}
-
-func TestGraphiteSplit(t *testing.T) {
-	str := "4.name#3.age#5.email"
-	strarr := GraphiteSplit(str, ".", "#")
-
-	if len(strarr) != 3 || strarr[0] != "name" || strarr[1] != "age" || strarr[2] != "email" {
-		t.Errorf("%v\n", strarr)
-	}
-
-	str = "5.name"
-	strarr = GraphiteSplit(str, ".", "#")
-	if len(strarr) != 0 {
-		t.Errorf("%v\n", strarr)
-	}
-
-	str = "name"
-	strarr = GraphiteSplit(str, ".", "#")
-	if len(strarr) != 0 {
-		t.Errorf("%v\n", strarr)
-	}
-
-	str = "4.email"
-	strarr = GraphiteSplit(str, ".", "#")
-	if strarr[0] != "emai" {
-		t.Errorf("%v\n", strarr)
-	}
-}
-
-func TestPanicToErr(t *testing.T) {
-	errstr := "panic"
-	f := func() { panic(errstr) }
-	var e error
-	PanicToErr(f, &e)
-	if e.Error() != errstr {
-		t.Errorf("expected %s, result %s", errstr, e.Error())
-	}
-
-	f = func() { panic(fmt.Errorf(errstr)) }
-	PanicToErr(f, &e)
-	if e.Error() != errstr {
-		t.Errorf("expected %s, result %s", errstr, e.Error())
-	}
-
-	f = func() { panic(nil) }
-	PanicToErr(f, nil)
-}
-
 func TestValidateName(t *testing.T) {
 	if err := ValidateName("localhost"); err != nil {
 		t.Errorf("error %v", err)
@@ -374,8 +38,21 @@ func TestValidateName(t *testing.T) {
 	}
 }
 
+func uuid() (string, error) {
+	uuid := make([]byte, 16)
+	n, err := io.ReadFull(rand.Reader, uuid)
+	if n != len(uuid) || err != nil {
+		return "", err
+	}
+
+	uuid[8] = uuid[8]&^0xc0 | 0x80
+	uuid[6] = uuid[6]&^0xf0 | 0x40
+
+	return fmt.Sprintf("%x-%x-%x-%x-%x", uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:]), nil
+}
+
 func TestDirFunc(t *testing.T) {
-	uuid, err := UUID()
+	uuid, err := uuid()
 	if err != nil {
 		t.Errorf("%v", err)
 	}
@@ -398,3 +75,33 @@ func TestDirFunc(t *testing.T) {
 	}
 
 }
+
+func TestIsDirEmpty(t *testing.T) {
+	name, _ := uuid()
+	if flag := IsDirEmpty(name); !flag {
+		t.Errorf("not exist dir is empty")
+	}
+}
+
+func TestExpandDir(t *testing.T) {
+	if !filepath.IsAbs(ExpandDir("testExpandDir")) {
+		t.Errorf("should return abs path")
+	}
+}
+
+func TestBackupAndCleanDir(t *testing.T) {
+	if BackupAndCleanDir("notexistdir") != nil {
+		t.Errorf("nil for not exist dir")
+	}
+}
+
+func TestNormalizeZapLogPath(t *testing.T) {
+	if runtime.GOOS != "windows" {
+		for i := 0; i < 10; i++ {
+			name, _ := uuid()
+			if NormalizeZapLogPath(name) != name {
+				t.Errorf("for non-windows should return same result")
+			}
+		}
+	}
+}
diff --git a/pkg/context/contexttest/context.go b/pkg/context/contexttest/context.go
index c11b08b9..8eb57bd3 100644
--- a/pkg/context/contexttest/context.go
+++ b/pkg/context/contexttest/context.go
@@ -18,6 +18,7 @@
 package contexttest
 
 import (
+	"sync"
 	"time"
 
 	"github.com/megaease/easegress/pkg/context"
@@ -28,6 +29,8 @@ import (
 
 // MockedHTTPContext is the mocked HTTP context
 type MockedHTTPContext struct {
+	lock                     sync.Mutex
+	finishFuncs              []func()
 	MockedLock               func()
 	MockedUnlock             func()
 	MockedSpan               func() tracing.Span
@@ -73,7 +76,7 @@ func (c *MockedHTTPContext) Span() tracing.Span {
 	if c.MockedSpan != nil {
 		return c.MockedSpan()
 	}
-	return nil
+	return tracing.NewSpan(tracing.NoopTracing, "mocked")
 }
 
 // Request mocks the Request function of HTTPContext
@@ -154,6 +157,9 @@ func (c *MockedHTTPContext) OnFinish(fn func()) {
 	if c.MockedFinish != nil {
 		c.MockedOnFinish(fn)
 	}
+	c.lock.Lock()
+	c.finishFuncs = append(c.finishFuncs, fn)
+	c.lock.Unlock()
 }
 
 // AddTag mocks the AddTag function of HTTPContext
@@ -183,7 +189,20 @@ func (c *MockedHTTPContext) Log() string {
 func (c *MockedHTTPContext) Finish() {
 	if c.MockedFinish != nil {
 		c.MockedFinish()
+		return
+	}
+
+	c.lock.Lock()
+	for _, fn := range c.finishFuncs {
+		func() {
+			defer func() {
+				if err := recover(); err != nil {
+				}
+			}()
+			fn()
+		}()
 	}
+	c.lock.Unlock()
 }
 
 // Template mocks the Template function of HTTPContext
diff --git a/pkg/filter/fallback/fallback_test.go b/pkg/filter/fallback/fallback_test.go
index 1253f187..03aa1da2 100644
--- a/pkg/filter/fallback/fallback_test.go
+++ b/pkg/filter/fallback/fallback_test.go
@@ -73,6 +73,11 @@ mockBody: "mocked body"
 		t.Error("header is not correct")
 	}
 
+	if fb.Status() != nil {
+		t.Error("behavior changed, please update this case")
+	}
+	fb.Description()
+
 	newFb := &Fallback{}
 	spec, _ = httppipeline.NewFilterSpec(rawSpec, nil)
 	newFb.Inherit(spec, fb)
diff --git a/pkg/filter/mock/mock_test.go b/pkg/filter/mock/mock_test.go
index e4ed7ed8..afac3f2a 100644
--- a/pkg/filter/mock/mock_test.go
+++ b/pkg/filter/mock/mock_test.go
@@ -42,16 +42,21 @@ func TestMock(t *testing.T) {
 kind: Mock
 name: mock
 rules:
+- pathPrefix: /login/
+  code: 202
+  body: 'mocked body'
+  headers:
+    X-Test: test1
 - path: /sales
   code: 203
   body: 'mocked body'
   headers:
-    X-Test: test1
+    X-Test: test2
   delay: 1ms
-- code: 203
+- code: 204
   body: 'mocked body 2'
   headers:
-    X-Test: test2
+    X-Test: test3
 `
 	rawSpec := make(map[string]interface{})
 	yamltool.Unmarshal([]byte(yamlSpec), &rawSpec)
@@ -68,9 +73,6 @@ rules:
 	ctx.MockedRequest.MockedMethod = func() string {
 		return http.MethodGet
 	}
-	ctx.MockedRequest.MockedPath = func() string {
-		return "/sales"
-	}
 	resp := httptest.NewRecorder()
 	ctx.MockedResponse.MockedSetStatusCode = func(code int) {
 		resp.WriteHeader(code)
@@ -86,6 +88,19 @@ rules:
 		return ""
 	}
 
+	resp = httptest.NewRecorder()
+	ctx.MockedRequest.MockedPath = func() string {
+		return "/login/1"
+	}
+	m.Handle(ctx)
+	if resp.Code != 202 {
+		t.Error("status code is not 202")
+	}
+
+	resp = httptest.NewRecorder()
+	ctx.MockedRequest.MockedPath = func() string {
+		return "/sales"
+	}
 	m.Handle(ctx)
 	if resp.Code != 203 {
 		t.Error("status code is not 203")
@@ -95,8 +110,8 @@ rules:
 		t.Error("body should be 'mocked body'")
 	}
 
-	if resp.Header().Get("X-Test") != "test1" {
-		t.Error("header 'X-Test' should be 'test1'")
+	if resp.Header().Get("X-Test") != "test2" {
+		t.Error("header 'X-Test' should be 'test2'")
 	}
 
 	if m.Status() != nil {
@@ -104,6 +119,7 @@ rules:
 	}
 	m.Description()
 
+	resp = httptest.NewRecorder()
 	ctx.MockedRequest.MockedPath = func() string {
 		return "/customer"
 	}
@@ -112,7 +128,7 @@ rules:
 	newM.Inherit(spec, m)
 	m.Close()
 	newM.Handle(ctx)
-	if resp.Code != 203 {
-		t.Error("status code is not 203")
+	if resp.Code != 204 {
+		t.Error("status code is not 204")
 	}
 }
diff --git a/pkg/filter/proxy/proxy_test.go b/pkg/filter/proxy/proxy_test.go
new file mode 100644
index 00000000..987e12e3
--- /dev/null
+++ b/pkg/filter/proxy/proxy_test.go
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2017, MegaEase
+ * All rights reserved.
+ *
+ * Licensed 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 proxy
+
+import (
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/megaease/easegress/pkg/context/contexttest"
+	"github.com/megaease/easegress/pkg/object/httppipeline"
+	"github.com/megaease/easegress/pkg/util/httpfilter"
+	"github.com/megaease/easegress/pkg/util/httpheader"
+	"github.com/megaease/easegress/pkg/util/memorycache"
+	"github.com/megaease/easegress/pkg/util/yamltool"
+)
+
+func TestProxy(t *testing.T) {
+	const yamlSpec = `
+name: proxy
+kind: Proxy
+fallback:
+  forCodes: true
+  mockCode: 200
+  mockHeaders:
+    X-Mock: mocked
+  mockBody: this is the mocked body
+mainPool:
+  servers:
+  - url: http://127.0.0.1:9095
+  - url: http://127.0.0.1:9096
+  - url: http://127.0.0.1:9097
+  loadBalance:
+    policy: roundRobin
+candidatePools:
+- filter:
+    headers:
+      "X-Test":
+        exact: testheader
+  servers:
+  - url: http://127.0.0.2:9095
+  - url: http://127.0.0.2:9096
+  - url: http://127.0.0.2:9097
+  - url: http://127.0.0.2:9098
+  loadBalance:
+    policy: roundRobin
+mirrorPool:
+  filter:
+    headers:
+      "X-Mirror":
+        exact: mirror
+  servers:
+  - url: http://127.0.0.3:9095
+  - url: http://127.0.0.3:9096
+  loadBalance:
+    policy: roundRobin
+compression:
+  minLength: 1024
+failureCodes: [503, 504]
+`
+	rawSpec := make(map[string]interface{})
+	yamltool.Unmarshal([]byte(yamlSpec), &rawSpec)
+
+	spec, e := httppipeline.NewFilterSpec(rawSpec, nil)
+	if e != nil {
+		t.Errorf("unexpected error: %v", e)
+	}
+
+	proxy := &Proxy{}
+	proxy.Init(spec)
+
+	if len(proxy.candidatePools) != 1 {
+		t.Error("length of candidate pools is incorrect")
+	}
+	if len(proxy.mirrorPool.spec.Servers) != 2 {
+		t.Error("server count of mirror pool is incorrect")
+	}
+
+	status := proxy.Status()
+	if status == nil {
+		t.Error("status should not be nil")
+	}
+
+	ctx := &contexttest.MockedHTTPContext{}
+	ctx.MockedResponse.MockedStatusCode = func() int {
+		return http.StatusServiceUnavailable
+	}
+	ctx.MockedRequest.MockedHeader = func() *httpheader.HTTPHeader {
+		header := http.Header{}
+		header.Set("X-Mirror", "mirror")
+		return httpheader.New(header)
+	}
+	ctx.MockedResponse.MockedHeader = func() *httpheader.HTTPHeader {
+		header := http.Header{}
+		return httpheader.New(header)
+	}
+	if !proxy.fallbackForCodes(ctx) {
+		t.Error("fallback for 503 should be true")
+	}
+	ctx.MockedResponse.MockedStatusCode = func() int {
+		return http.StatusInternalServerError
+	}
+	if proxy.fallbackForCodes(ctx) {
+		t.Error("fallback for 500 should be false")
+	}
+
+	fnSendRequest = func(r *http.Request) (*http.Response, error) {
+		return &http.Response{
+			Body: io.NopCloser(strings.NewReader("this is the body")),
+		}, nil
+	}
+	result := proxy.Handle(ctx)
+	if result != "" {
+		t.Error("proxy.Handle should succeeded")
+	}
+	ctx.Finish()
+
+	fnSendRequest = func(r *http.Request) (*http.Response, error) {
+		return nil, fmt.Errorf("mocked error")
+	}
+	result = proxy.Handle(ctx)
+	if result == "" {
+		t.Error("proxy.Handle should fail")
+	}
+
+	time.Sleep(10 * time.Millisecond)
+	proxy.Close()
+	time.Sleep(10 * time.Millisecond)
+}
+
+func TestSpecValidate(t *testing.T) {
+	spec := Spec{}
+
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.MainPool = &PoolSpec{}
+	spec.MainPool.Filter = &httpfilter.Spec{}
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.MainPool.Filter = nil
+	if spec.Validate() != nil {
+		t.Error("validate should succeed")
+	}
+
+	spec.CandidatePools = append(spec.CandidatePools, &PoolSpec{})
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.CandidatePools[0].Filter = &httpfilter.Spec{}
+	if spec.Validate() != nil {
+		t.Error("validate should succeed")
+	}
+
+	spec.MirrorPool = &PoolSpec{}
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.MirrorPool.Filter = &httpfilter.Spec{}
+	if spec.Validate() != nil {
+		t.Error("validate should succeed")
+	}
+
+	spec.MirrorPool.MemoryCache = &memorycache.Spec{}
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+	spec.MirrorPool.MemoryCache = nil
+
+	spec.Fallback = &FallbackSpec{}
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.FailureCodes = []int{500}
+	if spec.Validate() != nil {
+		t.Error("validate should succeed")
+	}
+}
+
+func TestPoolSpecValidate(t *testing.T) {
+	spec := PoolSpec{}
+
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	servers := []*Server{
+		{
+			URL:  "http://127.0.0.1:9090",
+			Tags: []string{"d1", "v1", "green"},
+		},
+		{
+			URL:    "http://127.0.0.1:9091",
+			Tags:   []string{"v1", "d1", "green"},
+			Weight: 2,
+		},
+	}
+	spec.Servers = servers
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	servers[0].Weight = 1
+	spec.ServersTags = []string{"v2"}
+	if spec.Validate() == nil {
+		t.Error("validate should fail")
+	}
+
+	spec.ServersTags = []string{"v1"}
+	if spec.Validate() != nil {
+		t.Error("validate should succeed")
+	}
+}
diff --git a/pkg/util/jmxtool/jmx_controller_test.go b/pkg/util/jmxtool/jmx_controller_test.go
deleted file mode 100644
index 88c35cee..00000000
--- a/pkg/util/jmxtool/jmx_controller_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (c) 2017, MegaEase
- * All rights reserved.
- *
- * Licensed 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 jmxtool
-
-import (
-	"fmt"
-	"testing"
-
-	"github.com/fatih/structs"
-	yamljsontool "github.com/ghodss/yaml"
-	"gopkg.in/yaml.v2"
-
-	"github.com/megaease/easegress/pkg/object/meshcontroller/spec"
-)
-
-type heapMemoryUsage struct {
-	committed int64
-	init      int64
-	max       int64
-	used      int64
-}
-
-func TestGetMbeanAttribute(t *testing.T) {
-	client := NewJolokiaClient("127.0.0.1", "8778", "jolokia")
-
-	// Read Value
-	oldThreadCount, _ := client.SetMbeanAttribute("com.easeagent.jmx:type=SystemConfig", "ThreadCount", "", 11)
-	fmt.Println(oldThreadCount)
-
-	// Set value
-	newThreadCount, _ := client.GetMbeanAttribute("com.easeagent.jmx:type=SystemConfig", "ThreadCount", "")
-	fmt.Println(newThreadCount)
-
-	newHeapMemoryUsage := heapMemoryUsage{
-		init:      0,
-		committed: 1234,
-		max:       9999,
-		used:      6666,
-	}
-
-	// Set value
-	oldMemoryUsage, _ := client.SetMbeanAttribute("com.easeagent.jmx:type=SystemConfig", "HeapMemoryUsage", "", newHeapMemoryUsage)
-	fmt.Println(oldMemoryUsage)
-
-	// Read sub field of mbean
-	newCommitted, _ := client.GetMbeanAttribute("com.easeagent.jmx:type=SystemConfig", "HeapMemoryUsage", "committed")
-	fmt.Println(newCommitted)
-
-	// List mbean
-	//mbeanDetail, err := client.ListMbean("com.easeagent.jmx:type=SystemConfig")
-	//if err != nil {
-	//	fmt.Println(err)
-	//}
-	//fmt.Println(mbeanDetail)
-	//
-	//// Search mbeans
-	//mbeans, err := client.SearchMbeans("com.easeagent.jmx:*")
-	//if err != nil {
-	//	fmt.Println(err)
-	//}
-	//fmt.Println(mbeans)
-}
-
-func TestExecuteMbeanOperation(t *testing.T) {
-	// Execute operation
-	client := NewJolokiaClient("127.0.0.1", "8778", "jolokia")
-
-	observability := spec.Observability{}
-	observability.OutputServer = &spec.ObservabilityOutputServer{
-		Enabled:         true,
-		BootstrapServer: "128.0.0.1",
-	}
-
-	observabilityTracingDetail := spec.ObservabilityTracingsDetail{
-		Enabled:       true,
-		ServicePrefix: "agent",
-	}
-
-	observability.Tracings = &spec.ObservabilityTracings{
-		SampleByQPS: 123,
-		Output: spec.ObservabilityTracingsOutputConfig{
-			Topic: "KAFKA",
-		},
-		Request:      observabilityTracingDetail,
-		RemoteInvoke: observabilityTracingDetail,
-		Kafka:        observabilityTracingDetail,
-		Jdbc:         observabilityTracingDetail,
-		Redis:        observabilityTracingDetail,
-		Rabbit:       observabilityTracingDetail,
-	}
-
-	observabilityMetricDetail := spec.ObservabilityMetricsDetail{
-		Enabled:  false,
-		Interval: 1,
-		Topic:    "aaa",
-	}
-	observability.Metrics = &spec.ObservabilityMetrics{
-		Request:        observabilityMetricDetail,
-		JdbcConnection: observabilityMetricDetail,
-		JdbcStatement:  observabilityMetricDetail,
-		Rabbit:         observabilityMetricDetail,
-		Redis:          observabilityMetricDetail,
-		Kafka:          observabilityMetricDetail,
-	}
-
-	m := structs.Map(observability)
-
-	args := []interface{}{m}
-	operation, err := client.ExecuteMbeanOperation("com.easeagent.jmx:type=SystemConfig", "updateConfigs", args)
-	if err != nil {
-		fmt.Println(err)
-	}
-	fmt.Println(operation)
-}
-
-func TestSpecTransform(t *testing.T) {
-	observability := spec.Observability{}
-	observability.OutputServer = &spec.ObservabilityOutputServer{
-		Enabled:         true,
-		BootstrapServer: "128.0.0.1",
-	}
-
-	observabilityTracingDetail := spec.ObservabilityTracingsDetail{
-		Enabled:       true,
-		ServicePrefix: "agent",
-	}
-
-	observability.Tracings = &spec.ObservabilityTracings{
-		SampleByQPS: 123,
-		Output: spec.ObservabilityTracingsOutputConfig{
-			Topic: "KAFKA",
-		},
-		Request:      observabilityTracingDetail,
-		RemoteInvoke: observabilityTracingDetail,
-		Kafka:        observabilityTracingDetail,
-		Jdbc:         observabilityTracingDetail,
-		Redis:        observabilityTracingDetail,
-		Rabbit:       observabilityTracingDetail,
-	}
-
-	observabilityMetricDetail := spec.ObservabilityMetricsDetail{
-		Enabled:  false,
-		Interval: 1,
-		Topic:    "aaa",
-	}
-	observability.Metrics = &spec.ObservabilityMetrics{
-		Request:        observabilityMetricDetail,
-		JdbcConnection: observabilityMetricDetail,
-		JdbcStatement:  observabilityMetricDetail,
-		Rabbit:         observabilityMetricDetail,
-		Redis:          observabilityMetricDetail,
-		Kafka:          observabilityMetricDetail,
-	}
-	service := spec.Service{
-		Observability:  &observability,
-		Name:           "service",
-		RegisterTenant: "order",
-		Resilience:     &spec.Resilience{},
-	}
-
-	buff, err := yaml.Marshal(service)
-	if err != nil {
-		fmt.Println(err)
-	}
-
-	jsonBytes, err := yamljsontool.YAMLToJSON(buff)
-	if err != nil {
-		fmt.Println(err)
-	}
-
-	kvMap, err := JSONToKVMap(string(jsonBytes))
-	for k, v := range kvMap {
-		fmt.Println(k, v)
-	}
-}
diff --git a/pkg/util/jsontool/jsontool_test.go b/pkg/util/jsontool/jsontool_test.go
index bb056f4c..3ec7a33f 100644
--- a/pkg/util/jsontool/jsontool_test.go
+++ b/pkg/util/jsontool/jsontool_test.go
@@ -32,3 +32,11 @@ func TestJSONTrimNull(t *testing.T) {
 		t.Fatalf("want: %s\ngot : %s", want, got)
 	}
 }
+
+func TestTrimNull(t *testing.T) {
+	_, err := TrimNull(nil)
+
+	if err == nil {
+		t.Errorf("trim null should be failed")
+	}
+}
diff --git a/pkg/util/ratelimiter/ratelimiter_test.go b/pkg/util/ratelimiter/ratelimiter_test.go
index 1ebc4703..d604152d 100644
--- a/pkg/util/ratelimiter/ratelimiter_test.go
+++ b/pkg/util/ratelimiter/ratelimiter_test.go
@@ -117,6 +117,24 @@ func TestRateLimiter(t *testing.T) {
 		}
 	}
 
+	limiter.SetState(StateDisabled)
+	if permitted, d := limiter.AcquirePermission(); !permitted {
+		t.Errorf("AcquirePermission should succeeded")
+	} else if d != 0 {
+		t.Errorf("wait duration should not be: %s", d.String())
+	}
+
+	limiter.SetState(StateNormal)
+	for i := 0; i < 30; i++ {
+		permitted, d := limiter.AcquirePermission()
+		if !permitted {
+			t.Errorf("AcquirePermission should succeed: %d", i)
+		}
+		if d != time.Duration(i/policy.LimitForPeriod)*policy.LimitRefreshPeriod {
+			t.Errorf("wait duration of %d should not be: %s", i, d.String())
+		}
+	}
+
 	limiter.SetState(StateLimiting)
 	if permitted, d := limiter.AcquirePermission(); permitted {
 		t.Errorf("AcquirePermission should fail")
@@ -124,6 +142,7 @@ func TestRateLimiter(t *testing.T) {
 		t.Errorf("wait duration should not be: %s", d.String())
 	}
 
+	limiter.SetState(StateLimiting)
 	now = now.Add(time.Millisecond * 5)
 	if permitted, d := limiter.AcquirePermission(); permitted {
 		t.Errorf("AcquirePermission should fail")
diff --git a/pkg/util/texttemplate/texttemplate_test.go b/pkg/util/texttemplate/texttemplate_test.go
index 70c2f432..7d5ad425 100644
--- a/pkg/util/texttemplate/texttemplate_test.go
+++ b/pkg/util/texttemplate/texttemplate_test.go
@@ -21,6 +21,102 @@ import (
 	"testing"
 )
 
+func TestNewFailed(t *testing.T) {
+	_, err := New("", "", "", []string{})
+
+	if err == nil {
+		t.Error("new template should not succ")
+	}
+}
+
+func TestNewFailedAtBuild(t *testing.T) {
+	_, err := New(DefaultBeginToken, DefaultEndToken, DefaultSeparator, []string{
+		"filter.xx.",
+	})
+	if err == nil {
+		t.Error("new template should not succ")
+	}
+}
+
+func TestSetDictFailed(t *testing.T) {
+	tt, err := NewDefault([]string{
+		"filter.{}.req.path",
+		"filter.{}.req.method",
+		"filter.{}.req.body",
+		"filter.{}.req.scheme",
+		"filter.{}.req.proto",
+		"filter.{}.req.host",
+		"filter.{}.req.body.{gjson}",
+		"filter.{}.req.header.{}",
+		"filter.{}.rsp.statuscode",
+		"filter.{}.rsp.body.{gjson}",
+	})
+	if err != nil {
+		t.Errorf("new engine failed err %v", err)
+	}
+
+	if err = tt.SetDict("no.abc.req.body", "kkk"); err == nil {
+		t.Errorf("set dict should failed")
+	}
+}
+
+func TestNewDefaultTextTemplateFailed(t *testing.T) {
+	tt, err := NewDefault([]string{
+		"filter.{}.req.path",
+		"filter.{}.req.method",
+		"filter.{}.req.body",
+		"filter.{}.req.scheme",
+		"filter.{}.req.proto",
+		"filter.{}.req.host",
+		"filter.{}.req.body.{gjson}",
+		"filter.{}.req.header.{}",
+		"filter.{}.rsp.statuscode",
+		"filter.{}.rsp.body.{gjson}",
+	})
+	if err != nil {
+		t.Errorf("new engine failed err %v", err)
+	}
+
+	if err = tt.SetDict("filter.abc.req.body", "kkk"); err != nil {
+		t.Errorf("set failed err =%v", err)
+	}
+
+	if s, _ := tt.Render("xxx-[[filter.abc.rsp.body]]--yyy"); s == "xxx-kkk--yyy" {
+		t.Errorf("rendering should fail , but succ")
+	}
+}
+
+func TestNewDefaultTextTemplateNothing(t *testing.T) {
+	tt, err := NewDefault([]string{
+		"filter.{}.req.path",
+		"filter.{}.req.method",
+		"filter.{}.req.body",
+		"filter.{}.req.scheme",
+		"filter.{}.req.proto",
+		"filter.{}.req.host",
+		"filter.{}.req.body.{gjson}",
+		"filter.{}.req.header.{}",
+		"filter.{}.rsp.statuscode",
+		"filter.{}.rsp.body.{gjson}",
+	})
+	if err != nil {
+		t.Errorf("new engine failed err %v", err)
+	}
+
+	if res := tt.MatchMetaTemplate("filter.abc.req.body"); len(res) == 0 {
+		t.Errorf("input %s match template %s", "filter.abc.req.body", res)
+	}
+
+	if err = tt.SetDict("filter.abc.req.body", "kkk"); err != nil {
+		t.Errorf("set failed err =%v", err)
+	}
+
+	if s, err := tt.Render("xxx---yyy"); s != "xxx---yyy" || err != nil {
+		t.Errorf("rendering fail , result is %s expect xxx---yyy", s)
+	}
+
+}
+
 func TestNewDefaultTextTemplateSucc(t *testing.T) {
 	tt, err := NewDefault([]string{
 		"filter.{}.req.path",
@@ -49,6 +145,33 @@ func TestNewDefaultTextTemplateSucc(t *testing.T) {
 	if s, err := tt.Render("xxx-[[filter.abc.req.body]]--yyy"); s != "xxx-kkk--yyy" || err != nil {
 		t.Errorf("rendering fail , result is %s expect xxx-kkk--yyy", s)
 	}
+
+	dict := tt.GetDict()
+	if len(dict) == 0 {
+		t.Error("get dict failed, should not empty")
+	}
+}
+
+func TestNewDefaultTextTemplateEmpty(t *testing.T) {
+	tt, err := NewDefault([]string{
+		"filter.{}.req.path",
+		"filter.{}.req.method",
+		"filter.{}.req.body",
+		"filter.{}.req.scheme",
+		"filter.{}.req.proto",
+		"filter.{}.req.host",
+		"filter.{}.req.body.{gjson}",
+		"filter.{}.req.header.{}",
+		"filter.{}.rsp.statuscode",
+		"filter.{}.rsp.body.{gjson}",
+	})
+	if err != nil {
+		t.Errorf("new engine failed err %v", err)
+	}
+
+	if s, _ := tt.Render("xxx-[[filter.abc.req.body]]--yyy"); s != "xxx---yyy" {
+		t.Errorf("rendering fail , result is %s expect xxx---yyy", s)
+	}
 }
 
 func TestDummyTemplate_render(t *testing.T) {
diff --git a/pkg/util/urlrule/urlrule_test.go b/pkg/util/urlrule/urlrule_test.go
index f6b9b5c4..59d9cf71 100644
--- a/pkg/util/urlrule/urlrule_test.go
+++ b/pkg/util/urlrule/urlrule_test.go
@@ -236,6 +236,15 @@ func TestURLRULENoMatchURL(t *testing.T) {
 	}
 }
 
+func TestFailStringMatch(t *testing.T) {
+	sm := StringMatch{}
+
+	err := sm.Validate()
+	if err == nil {
+		t.Error("string matcher should be invalid")
+	}
+}
+
 func TestDeepEqual(t *testing.T) {
 	u1 := &URLRule{
 		id: "TestDeepEqual1",
@@ -282,6 +291,42 @@ func TestDeepEqual(t *testing.T) {
 		},
 	}
 
+	u5 := &URLRule{
+		id: "TestDeepEqual1",
+		Methods: []string{
+			"GET",
+			"POST",
+		},
+		URL: StringMatch{
+			Exact: "/app/v3/user",
+		},
+	}
+
+	u6 := &URLRule{
+		id: "TestDeepEqual2",
+		Methods: []string{
+			"GET",
+			"POST",
+		},
+		URL: StringMatch{
+			Exact:  "/app/v2/user",
+			Prefix: "/app",
+		},
+	}
+
+	u7 := &URLRule{
+		id: "TestDeepEqual2",
+		Methods: []string{
+			"GET",
+			"POST",
+		},
+		URL: StringMatch{
+			Exact:  "/app/v2/user",
+			Prefix: "/app",
+			RegEx:  "^.*$",
+		},
+	}
+
 	if !u1.DeepEqual(u2) {
 		t.Errorf("%v and %v should be equal\n", u1, u2)
 	}
@@ -291,4 +336,16 @@ func TestDeepEqual(t *testing.T) {
 	if u3.DeepEqual(u4) {
 		t.Errorf("%v and %v should not be equal\n", u3, u4)
 	}
+
+	if u1.DeepEqual(u5) {
+		t.Errorf("%v and %v should not be equal\n", u1, u5)
+	}
+
+	if u1.DeepEqual(u6) {
+		t.Errorf("%v and %v should not be equal\n", u1, u6)
+	}
+
+	if u6.DeepEqual(u7) {
+		t.Errorf("%v and %v should not be equal\n", u6, u7)
+	}
 }