Repository: go-kratos/kratos
Pull request #2140 (2022-06-28 14:39:03)

test(contrib): add unit test for contrib/config/k8s
Description (what this PR does / why we need it):
coverage: 90+% of statements

Which issue(s) this PR fixes (resolves / be part of):
part of #2135 

codecov-commenter: # [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2140?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) Report
> Merging [#2140](https://codecov.io/gh/go-kratos/kratos/pull/2140?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (6320363) into [main](https://codecov.io/gh/go-kratos/kratos/commit/13c33fc4d34381f1a3e92a065ebb26628910caa6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos) (13c33fc) will **not change** coverage.
> The diff coverage is `n/a`.

```diff
@@           Coverage Diff           @@
##             main    #2140   +/-   ##
=======================================
  Coverage   79.08%   79.08%           
=======================================
  Files          84       84           
  Lines        3696     3696           
=======================================
  Hits         2923     2923           
  Misses        560      560           
  Partials      213      213           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2140?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos).
> **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=go-kratos)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/go-kratos/kratos/pull/2140?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). Last update [13c33fc...6320363](https://codecov.io/gh/go-kratos/kratos/pull/2140?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=go-kratos). 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=go-kratos).


Patch:
diff --git a/cmd/kratos/internal/base/repo.go b/cmd/kratos/internal/base/repo.go
index cd7b650f..55b12ac0 100644
--- a/cmd/kratos/internal/base/repo.go
+++ b/cmd/kratos/internal/base/repo.go
@@ -77,10 +77,10 @@ func (r *Repo) Pull(ctx context.Context) error {
 	cmd = exec.CommandContext(ctx, "git", "pull")
 	cmd.Dir = r.Path()
 	out, err := cmd.CombinedOutput()
+	fmt.Println(string(out))
 	if err != nil {
 		return err
 	}
-	fmt.Println(string(out))
 	return err
 }
 
@@ -96,10 +96,10 @@ func (r *Repo) Clone(ctx context.Context) error {
 		cmd = exec.CommandContext(ctx, "git", "clone", "-b", r.branch, r.url, r.Path())
 	}
 	out, err := cmd.CombinedOutput()
+	fmt.Println(string(out))
 	if err != nil {
 		return err
 	}
-	fmt.Println(string(out))
 	return nil
 }
 
diff --git a/cmd/kratos/internal/proto/proto.go b/cmd/kratos/internal/proto/proto.go
index c23e5a83..4b9c858b 100644
--- a/cmd/kratos/internal/proto/proto.go
+++ b/cmd/kratos/internal/proto/proto.go
@@ -13,7 +13,6 @@ var CmdProto = &cobra.Command{
 	Use:   "proto",
 	Short: "Generate the proto files",
 	Long:  "Generate the proto files.",
-	Run:   run,
 }
 
 func init() {
@@ -21,6 +20,3 @@ func init() {
 	CmdProto.AddCommand(client.CmdClient)
 	CmdProto.AddCommand(server.CmdServer)
 }
-
-func run(cmd *cobra.Command, args []string) {
-}
diff --git a/contrib/registry/etcd/watcher.go b/contrib/registry/etcd/watcher.go
index d04f4fbd..22e63474 100644
--- a/contrib/registry/etcd/watcher.go
+++ b/contrib/registry/etcd/watcher.go
@@ -29,7 +29,7 @@ func newWatcher(ctx context.Context, key, name string, client *clientv3.Client)
 		serviceName: name,
 	}
 	w.ctx, w.cancel = context.WithCancel(ctx)
-	w.watchChan = w.watcher.Watch(w.ctx, key, clientv3.WithPrefix(), clientv3.WithRev(0))
+	w.watchChan = w.watcher.Watch(w.ctx, key, clientv3.WithPrefix(), clientv3.WithRev(0), clientv3.WithKeysOnly())
 	err := w.watcher.RequestProgress(context.Background())
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/contrib/config/kubernetes/config_test.go b/contrib/config/kubernetes/config_test.go
index bd0a605a..6cdb1c98 100644
--- a/contrib/config/kubernetes/config_test.go
+++ b/contrib/config/kubernetes/config_test.go
@@ -1,18 +1,44 @@
 package kubernetes
 
 import (
+	"context"
 	"log"
 	"path/filepath"
+	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/go-kratos/kratos/v2/config"
+
+	v1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/util/homedir"
 )
 
+const (
+	testKey   = "test_config.json"
+	namespace = "default"
+	name      = "test"
+)
+
+var (
+	keyPath    = strings.Join([]string{namespace, name, testKey}, "/")
+	objectMeta = metav1.ObjectMeta{
+		Name:      name,
+		Namespace: namespace,
+		Labels: map[string]string{
+			"app": "test",
+		},
+	}
+)
+
 func TestSource(t *testing.T) {
 	home := homedir.HomeDir()
 	s := NewSource(
-		Namespace("mesh"),
+		Namespace("default"),
 		LabelSelector(""),
 		KubeConfig(filepath.Join(home, ".kube", "config")),
 	)
@@ -40,3 +66,143 @@ func ExampleNewSource() {
 		log.Panic(err)
 	}
 }
+
+func TestConfig(t *testing.T) {
+	restConfig, err := rest.InClusterConfig()
+	home := homedir.HomeDir()
+
+	options := []Option{
+		Namespace(namespace),
+		LabelSelector("app=test"),
+	}
+
+	if err != nil {
+		kubeconfig := filepath.Join(home, ".kube", "config")
+		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+		if err != nil {
+			t.Fatal(err)
+		}
+		options = append(options, KubeConfig(kubeconfig))
+	}
+	clientSet, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
+
+	source := NewSource(options...)
+	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: "test config",
+		},
+	}, metav1.CreateOptions{}); err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
+			t.Error(err)
+		}
+	}()
+	kvs, err := source.Load()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "test config" {
+		t.Fatal("config error")
+	}
+
+	w, err := source.Watch()
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		_ = w.Stop()
+	}()
+	// create also produce an event, discard it
+	if _, err = w.Next(); err != nil {
+		t.Fatal(err)
+	}
+
+	if _, err = clientSetConfigMaps.Update(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: "new config",
+		},
+	}, metav1.UpdateOptions{}); err != nil {
+		t.Error(err)
+	}
+
+	if kvs, err = w.Next(); err != nil {
+		t.Fatal(err)
+	}
+
+	if len(kvs) != 1 || kvs[0].Key != keyPath || string(kvs[0].Value) != "new config" {
+		t.Fatal("config error")
+	}
+}
+
+func TestExtToFormat(t *testing.T) {
+	restConfig, err := rest.InClusterConfig()
+	home := homedir.HomeDir()
+
+	options := []Option{
+		Namespace(namespace),
+		LabelSelector("app=test"),
+	}
+
+	if err != nil {
+		kubeconfig := filepath.Join(home, ".kube", "config")
+		restConfig, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+		if err != nil {
+			t.Fatal(err)
+		}
+		options = append(options, KubeConfig(kubeconfig))
+	}
+	clientSet, err := kubernetes.NewForConfig(restConfig)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	clientSetConfigMaps := clientSet.CoreV1().ConfigMaps(namespace)
+
+	tc := `{"a":1}`
+	if _, err = clientSetConfigMaps.Create(context.Background(), &v1.ConfigMap{
+		ObjectMeta: objectMeta,
+		Data: map[string]string{
+			testKey: tc,
+		},
+	}, metav1.CreateOptions{}); err != nil {
+		t.Fatal(err)
+	}
+
+	defer func() {
+		if err = clientSetConfigMaps.Delete(context.Background(), name, metav1.DeleteOptions{}); err != nil {
+			t.Error(err)
+		}
+	}()
+
+	source := NewSource(options...)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	kvs, err := source.Load()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(len(kvs), 1) {
+		t.Errorf("len(kvs) = %d", len(kvs))
+	}
+	if !reflect.DeepEqual(keyPath, kvs[0].Key) {
+		t.Errorf("kvs[0].Key is %s", kvs[0].Key)
+	}
+	if !reflect.DeepEqual(tc, string(kvs[0].Value)) {
+		t.Errorf("kvs[0].Value is %s", kvs[0].Value)
+	}
+	if !reflect.DeepEqual("json", kvs[0].Format) {
+		t.Errorf("kvs[0].Format is %s", kvs[0].Format)
+	}
+}
diff --git a/contrib/log/fluent/fluent_test.go b/contrib/log/fluent/fluent_test.go
index 8558f31c..5b7b3e17 100644
--- a/contrib/log/fluent/fluent_test.go
+++ b/contrib/log/fluent/fluent_test.go
@@ -5,6 +5,7 @@ import (
 	"net"
 	"os"
 	"testing"
+	"time"
 
 	"github.com/go-kratos/kratos/v2/log"
 )
@@ -29,6 +30,96 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
+func TestWithTimeout(t *testing.T) {
+	opts := new(options)
+	var duration time.Duration = 1000000000
+	funcTimeout := WithTimeout(duration)
+	funcTimeout(opts)
+	if opts.timeout != duration {
+		t.Errorf("WithTimeout() = %v, want %v", opts.timeout, duration)
+	}
+}
+
+func TestWithWriteTimeout(t *testing.T) {
+	opts := new(options)
+	var duration time.Duration = 1000000000
+	funcWriteTimeout := WithWriteTimeout(duration)
+	funcWriteTimeout(opts)
+	if opts.writeTimeout != duration {
+		t.Errorf("WithWriteTimeout() = %v, want %v", opts.writeTimeout, duration)
+	}
+}
+
+func TestWithBufferLimit(t *testing.T) {
+	opts := new(options)
+	bufferLimit := 1000000000
+	funcBufferLimit := WithBufferLimit(bufferLimit)
+	funcBufferLimit(opts)
+	if opts.bufferLimit != bufferLimit {
+		t.Errorf("WithBufferLimit() = %d, want %d", opts.bufferLimit, bufferLimit)
+	}
+}
+
+func TestWithRetryWait(t *testing.T) {
+	opts := new(options)
+	retryWait := 1000000000
+	funcRetryWait := WithRetryWait(retryWait)
+	funcRetryWait(opts)
+	if opts.retryWait != retryWait {
+		t.Errorf("WithRetryWait() = %d, want %d", opts.retryWait, retryWait)
+	}
+}
+
+func TestWithMaxRetry(t *testing.T) {
+	opts := new(options)
+	maxRetry := 1000000000
+	funcMaxRetry := WithMaxRetry(maxRetry)
+	funcMaxRetry(opts)
+	if opts.maxRetry != maxRetry {
+		t.Errorf("WithMaxRetry() = %d, want %d", opts.maxRetry, maxRetry)
+	}
+}
+
+func TestWithMaxRetryWait(t *testing.T) {
+	opts := new(options)
+	maxRetryWait := 1000000000
+	funcMaxRetryWait := WithMaxRetryWait(maxRetryWait)
+	funcMaxRetryWait(opts)
+	if opts.maxRetryWait != maxRetryWait {
+		t.Errorf("WithMaxRetryWait() = %d, want %d", opts.maxRetryWait, maxRetryWait)
+	}
+}
+
+func TestWithTagPrefix(t *testing.T) {
+	opts := new(options)
+	tagPrefix := "tag_prefix"
+	funcTagPrefix := WithTagPrefix(tagPrefix)
+	funcTagPrefix(opts)
+	if opts.tagPrefix != tagPrefix {
+		t.Errorf("WithTagPrefix() = %s, want %s", opts.tagPrefix, tagPrefix)
+	}
+}
+
+func TestWithAsync(t *testing.T) {
+	opts := new(options)
+	async := true
+	funcAsync := WithAsync(async)
+	funcAsync(opts)
+	if opts.async != async {
+		t.Errorf("WithAsync() = %t, want %t", opts.async, async)
+	}
+}
+
+func TestWithForceStopAsyncSend(t *testing.T) {
+	opts := new(options)
+	forceStopAsyncSend := true
+	funcForceStopAsyncSend := WithForceStopAsyncSend(forceStopAsyncSend)
+	funcForceStopAsyncSend(opts)
+	if opts.forceStopAsyncSend != forceStopAsyncSend {
+		t.Errorf("WithForceStopAsyncSend() = %t, want %t", opts.forceStopAsyncSend, forceStopAsyncSend)
+	}
+}
+
 func TestLogger(t *testing.T) {
 	logger, err := NewLogger("tcp://127.0.0.1:24224")
 	if err != nil {
@@ -43,6 +134,38 @@ func TestLogger(t *testing.T) {
 	flog.Error("log", "test")
 }
 
+func TestLoggerWithOpt(t *testing.T) {
+	var duration time.Duration = 1000000000
+	logger, err := NewLogger("tcp://127.0.0.1:24224", WithTimeout(duration))
+	if err != nil {
+		t.Error(err)
+	}
+	defer logger.Close()
+	flog := log.NewHelper(logger)
+
+	flog.Debug("log", "test")
+	flog.Info("log", "test")
+	flog.Warn("log", "test")
+	flog.Error("log", "test")
+}
+
+func TestLoggerError(t *testing.T) {
+	errCase := []string{
+		"foo",
+		"tcp://127.0.0.1/",
+		"tcp://127.0.0.1:1234a",
+		"tcp://127.0.0.1:65535",
+		"https://127.0.0.1:8080",
+		"unix://foo/bar",
+	}
+	for _, errc := range errCase {
+		_, err := NewLogger(errc)
+		if err == nil {
+			t.Error(err)
+		}
+	}
+}
+
 func BenchmarkLoggerPrint(b *testing.B) {
 	b.SetParallelism(100)
 	logger, err := NewLogger("tcp://127.0.0.1:24224")
diff --git a/contrib/opensergo/opensergo_test.go b/contrib/opensergo/opensergo_test.go
new file mode 100644
index 00000000..5c752d1c
--- /dev/null
+++ b/contrib/opensergo/opensergo_test.go
@@ -0,0 +1,385 @@
+package opensergo
+
+import (
+	"io/ioutil"
+	"net"
+	"os"
+	"path/filepath"
+	"reflect"
+	"testing"
+
+	srvContractPb "github.com/opensergo/opensergo-go/proto/service_contract/v1"
+	"golang.org/x/net/context"
+	"google.golang.org/genproto/googleapis/api/annotations"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/proto"
+	"google.golang.org/protobuf/reflect/protodesc"
+	pref "google.golang.org/protobuf/reflect/protoreflect"
+	"google.golang.org/protobuf/reflect/protoregistry"
+	"google.golang.org/protobuf/types/descriptorpb"
+)
+
+type testMetadataServiceServer struct {
+	srvContractPb.UnimplementedMetadataServiceServer
+}
+
+func (m *testMetadataServiceServer) ReportMetadata(ctx context.Context, req *srvContractPb.ReportMetadataRequest) (*srvContractPb.ReportMetadataReply, error) {
+	return &srvContractPb.ReportMetadataReply{}, nil
+}
+
+type testAppInfo struct {
+	id       string
+	name     string
+	version  string
+	metaData map[string]string
+	endpoint []string
+}
+
+func (t testAppInfo) ID() string {
+	return t.id
+}
+
+func (t testAppInfo) Name() string {
+	return t.name
+}
+
+func (t testAppInfo) Version() string {
+	return t.version
+}
+
+func (t testAppInfo) Metadata() map[string]string {
+	return t.metaData
+}
+
+func (t testAppInfo) Endpoint() []string {
+	return t.endpoint
+}
+
+func TestWithEndpoint(t *testing.T) {
+	o := &options{}
+	v := "127.0.0.1:9090"
+	WithEndpoint(v)(o)
+	if !reflect.DeepEqual(v, o.Endpoint) {
+		t.Fatalf("o.Endpoint:%s is not equal to v:%s", o.Endpoint, v)
+	}
+}
+
+func TestOptionsParseJSON(t *testing.T) {
+	want := &options{
+		Endpoint: "127.0.0.1:9090",
+	}
+	o := &options{}
+	if err := o.ParseJSON([]byte(`{"endpoint":"127.0.0.1:9090"}`)); err != nil {
+		t.Fatalf("o.ParseJSON(v) error:%s", err)
+	}
+	if !reflect.DeepEqual(o, want) {
+		t.Fatalf("o:%v is not equal to want:%v", o, want)
+	}
+}
+
+func TestListDescriptors(t *testing.T) {
+	testPb := &descriptorpb.FileDescriptorProto{
+		Syntax:  proto.String("proto3"),
+		Name:    proto.String("test.proto"),
+		Package: proto.String("test"),
+		MessageType: []*descriptorpb.DescriptorProto{
+			{
+				Name: proto.String("TestMessage"),
+				Field: []*descriptorpb.FieldDescriptorProto{
+					{
+						Name:     proto.String("id"),
+						JsonName: proto.String("id"),
+						Number:   proto.Int32(1),
+						Type:     descriptorpb.FieldDescriptorProto_Type(pref.Int32Kind).Enum(),
+					},
+					{
+						Name:     proto.String("name"),
+						JsonName: proto.String("name"),
+						Number:   proto.Int32(2),
+						Type:     descriptorpb.FieldDescriptorProto_Type(pref.StringKind).Enum(),
+					},
+				},
+			},
+		},
+		Service: []*descriptorpb.ServiceDescriptorProto{
+			{
+				Name: proto.String("TestService"),
+				Method: []*descriptorpb.MethodDescriptorProto{
+					{
+						Name:       proto.String("Create"),
+						InputType:  proto.String("TestMessage"),
+						OutputType: proto.String("TestMessage"),
+					},
+				},
+			},
+		},
+	}
+
+	fd, err := protodesc.NewFile(testPb, nil)
+	if err != nil {
+		t.Fatalf("protodesc.NewFile(pb, nil) error:%s", err)
+	}
+
+	protoregistry.GlobalFiles = new(protoregistry.Files)
+	err = protoregistry.GlobalFiles.RegisterFile(fd)
+	if err != nil {
+		t.Fatalf("protoregistry.GlobalFiles.RegisterFile(fd) error:%s", err)
+	}
+
+	want := struct {
+		services []*srvContractPb.ServiceDescriptor
+		types    []*srvContractPb.TypeDescriptor
+	}{
+		services: []*srvContractPb.ServiceDescriptor{
+			{
+				Name: "TestService",
+				Methods: []*srvContractPb.MethodDescriptor{
+					{
+						Name:            "Create",
+						InputTypes:      []string{"test.TestMessage"},
+						OutputTypes:     []string{"test.TestMessage"},
+						ClientStreaming: proto.Bool(false),
+						ServerStreaming: proto.Bool(false),
+						Description:     nil,
+						HttpPaths:       []string{""},
+						HttpMethods:     []string{""},
+					},
+				},
+			},
+		},
+		types: []*srvContractPb.TypeDescriptor{
+			{
+				Name: "TestMessage",
+				Fields: []*srvContractPb.FieldDescriptor{
+					{
+						Name:     "id",
+						Number:   int32(1),
+						Type:     srvContractPb.FieldDescriptor_TYPE_INT32,
+						TypeName: proto.String("int32"),
+					},
+					{
+						Name:     "name",
+						Number:   int32(2),
+						Type:     srvContractPb.FieldDescriptor_TYPE_STRING,
+						TypeName: proto.String("string"),
+					},
+				},
+			},
+		},
+	}
+
+	services, types, err := listDescriptors()
+	if err != nil {
+		t.Fatalf("listDescriptors error:%s", err)
+	}
+
+	if !reflect.DeepEqual(services, want.services) {
+		t.Fatalf("services:%v is not equal to want.services:%v", services, want.services)
+	}
+	if !reflect.DeepEqual(types, want.types) {
+		t.Fatalf("types:%v is not equal to want.types:%v", types, want.types)
+	}
+}
+
+func TestHTTPPatternInfo(t *testing.T) {
+	type args struct {
+		pattern interface{}
+	}
+	tests := []struct {
+		name       string
+		args       args
+		wantMethod string
+		wantPath   string
+	}{
+		{
+			name: "get",
+			args: args{
+				pattern: &annotations.HttpRule_Get{Get: "/foo"},
+			},
+			wantMethod: "GET",
+			wantPath:   "/foo",
+		},
+		{
+			name: "post",
+			args: args{
+				pattern: &annotations.HttpRule_Post{Post: "/foo"},
+			},
+			wantMethod: "POST",
+			wantPath:   "/foo",
+		},
+		{
+			name: "put",
+			args: args{
+				pattern: &annotations.HttpRule_Put{Put: "/foo"},
+			},
+			wantMethod: "PUT",
+			wantPath:   "/foo",
+		},
+		{
+			name: "delete",
+			args: args{
+				pattern: &annotations.HttpRule_Delete{Delete: "/foo"},
+			},
+			wantMethod: "DELETE",
+			wantPath:   "/foo",
+		},
+		{
+			name: "patch",
+			args: args{
+				pattern: &annotations.HttpRule_Patch{Patch: "/foo"},
+			},
+			wantMethod: "PATCH",
+			wantPath:   "/foo",
+		},
+		{
+			name: "custom",
+			args: args{
+				pattern: &annotations.HttpRule_Custom{
+					Custom: &annotations.CustomHttpPattern{
+						Kind: "CUSTOM",
+						Path: "/foo",
+					},
+				},
+			},
+			wantMethod: "CUSTOM",
+			wantPath:   "/foo",
+		},
+		{
+			name: "other",
+			args: args{
+				pattern: nil,
+			},
+			wantMethod: "",
+			wantPath:   "",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotMethod, gotPath := HTTPPatternInfo(tt.args.pattern)
+			if gotMethod != tt.wantMethod {
+				t.Errorf("HTTPPatternInfo() gotMethod = %v, want %v", gotMethod, tt.wantMethod)
+			}
+			if gotPath != tt.wantPath {
+				t.Errorf("HTTPPatternInfo() gotPath = %v, want %v", gotPath, tt.wantPath)
+			}
+		})
+	}
+}
+
+func TestOpenSergo(t *testing.T) {
+	srv := grpc.NewServer()
+	srvContractPb.RegisterMetadataServiceServer(srv, new(testMetadataServiceServer))
+	lis, err := net.Listen("tcp", "127.0.0.1:9090")
+	if err != nil {
+		t.Fatalf("net.Listen error:%s", err)
+	}
+	go func() {
+		err := srv.Serve(lis)
+		if err != nil {
+			panic(err)
+		}
+	}()
+
+	app := &testAppInfo{
+		name:     "testApp",
+		endpoint: []string{"//example.com:9090", "//foo.com:9090"},
+	}
+
+	type args struct {
+		opts []Option
+	}
+	tests := []struct {
+		name      string
+		args      args
+		preFunc   func(t *testing.T)
+		deferFunc func(t *testing.T)
+		wantErr   bool
+	}{
+		{
+			name: "test_with_opts",
+			args: args{
+				opts: []Option{
+					WithEndpoint("127.0.0.1:9090"),
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "test_with_env_endpoint",
+			args: args{
+				opts: []Option{},
+			},
+			preFunc: func(t *testing.T) {
+				err := os.Setenv("OPENSERGO_ENDPOINT", "127.0.0.1:9090")
+				if err != nil {
+					panic(err)
+				}
+			},
+			wantErr: false,
+		},
+		{
+			name: "test_with_env_config_file",
+			args: args{
+				opts: []Option{},
+			},
+			preFunc: func(t *testing.T) {
+				err := os.Setenv("OPENSERGO_BOOTSTRAP", `{"endpoint": "127.0.0.1:9090"}`)
+				if err != nil {
+					panic(err)
+				}
+			},
+			wantErr: false,
+		},
+		{
+			name: "test_with_env_bootstrap",
+			args: args{
+				opts: []Option{},
+			},
+			preFunc: func(t *testing.T) {
+				fileContent := `{"endpoint": "127.0.0.1:9090"}`
+				err := ioutil.WriteFile("test.json", []byte(fileContent), 0o644)
+				if err != nil {
+					t.Fatalf("ioutil.WriteFile error:%s", err)
+				}
+				confPath, err := filepath.Abs("./test.json")
+				if err != nil {
+					t.Fatalf("filepath.Abs error:%s", err)
+				}
+				err = os.Setenv("OPENSERGO_BOOTSTRAP_CONFIG", confPath)
+				if err != nil {
+					panic(err)
+				}
+			},
+			deferFunc: func(t *testing.T) {
+				path := os.Getenv("OPENSERGO_BOOTSTRAP_CONFIG")
+				if path != "" {
+					err := os.Remove(path)
+					if err != nil {
+						t.Fatalf("os.Remove error:%s", err)
+					}
+				}
+			},
+			wantErr: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.preFunc != nil {
+				tt.preFunc(t)
+			}
+			if tt.deferFunc != nil {
+				defer tt.deferFunc(t)
+			}
+			osServer, err := New(tt.args.opts...)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			err = osServer.ReportMetadata(context.Background(), app)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("ReportMetadata() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+		})
+	}
+}
diff --git a/contrib/registry/consul/registry_test.go b/contrib/registry/consul/registry_test.go
index e6b85e7d..483faf1a 100644
--- a/contrib/registry/consul/registry_test.go
+++ b/contrib/registry/consul/registry_test.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 	"net"
 	"reflect"
-	"strconv"
 	"testing"
 	"time"
 
@@ -24,7 +23,123 @@ func tcpServer(t *testing.T, lis net.Listener) {
 	}
 }
 
-func TestRegister(t *testing.T) {
+func TestRegistry_Register(t *testing.T) {
+	opts := []Option{
+		WithHealthCheck(false),
+	}
+
+	type args struct {
+		ctx        context.Context
+		serverName string
+		server     []*registry.ServiceInstance
+	}
+
+	test := []struct {
+		name    string
+		args    args
+		want    []*registry.ServiceInstance
+		wantErr bool
+	}{
+		{
+			name: "normal",
+			args: args{
+				ctx:        context.Background(),
+				serverName: "server-1",
+				server: []*registry.ServiceInstance{
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.1",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+				},
+			},
+			want: []*registry.ServiceInstance{
+				{
+					ID:        "1",
+					Name:      "server-1",
+					Version:   "v0.0.1",
+					Metadata:  nil,
+					Endpoints: []string{"http://127.0.0.1:8000"},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "registry new service replace old service",
+			args: args{
+				ctx:        context.Background(),
+				serverName: "server-1",
+				server: []*registry.ServiceInstance{
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.1",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+					{
+						ID:        "1",
+						Name:      "server-1",
+						Version:   "v0.0.2",
+						Metadata:  nil,
+						Endpoints: []string{"http://127.0.0.1:8000"},
+					},
+				},
+			},
+			want: []*registry.ServiceInstance{
+				{
+					ID:        "1",
+					Name:      "server-1",
+					Version:   "v0.0.2",
+					Metadata:  nil,
+					Endpoints: []string{"http://127.0.0.1:8000"},
+				},
+			},
+			wantErr: false,
+		},
+	}
+
+	for _, tt := range test {
+		t.Run(tt.name, func(t *testing.T) {
+			cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500"})
+			if err != nil {
+				t.Fatalf("create consul client failed: %v", err)
+			}
+
+			r := New(cli, opts...)
+
+			for _, instance := range tt.args.server {
+				err = r.Register(tt.args.ctx, instance)
+				if err != nil {
+					t.Error(err)
+				}
+			}
+
+			watch, err := r.Watch(tt.args.ctx, tt.args.serverName)
+			if err != nil {
+				t.Error(err)
+			}
+			got, err := watch.Next()
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("GetService() got = %v", got)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("GetService() got = %v, want %v", got, tt.want)
+			}
+
+			for _, instance := range tt.args.server {
+				_ = r.Deregister(tt.args.ctx, instance)
+			}
+		})
+	}
+}
+
+func TestRegistry_GetService(t *testing.T) {
 	addr := fmt.Sprintf("%s:9091", getIntranetIP())
 	lis, err := net.Listen("tcp", addr)
 	if err != nil {
@@ -44,47 +159,212 @@ func TestRegister(t *testing.T) {
 		WithHealthCheckInterval(5),
 	}
 	r := New(cli, opts...)
-	if err != nil {
-		t.Errorf("new consul registry failed: %v", err)
-	}
-	version := strconv.FormatInt(time.Now().Unix(), 10)
-	svc := &registry.ServiceInstance{
-		ID:        "test2233",
-		Name:      "test-provider",
-		Version:   version,
-		Metadata:  map[string]string{"app": "kratos"},
+
+	instance1 := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "server-1",
+		Version:   "v0.0.1",
 		Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)},
 	}
-	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
-	defer cancel()
-	err = r.Register(ctx, svc)
-	if err != nil {
-		t.Errorf("Register failed: %v", err)
+
+	type fields struct {
+		registry *Registry
 	}
-	w, err := r.Watch(ctx, "test-provider")
-	if err != nil {
-		t.Errorf("Watchfailed: %v", err)
+	type args struct {
+		ctx         context.Context
+		serviceName string
+	}
+	tests := []struct {
+		name      string
+		fields    fields
+		args      args
+		want      []*registry.ServiceInstance
+		wantErr   bool
+		preFunc   func(t *testing.T)
+		deferFunc func(t *testing.T)
+	}{
+		{
+			name:   "normal",
+			fields: fields{r},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: "server-1",
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				if err := r.Register(context.Background(), instance1); err != nil {
+					t.Error(err)
+				}
+				watch, err := r.Watch(context.Background(), instance1.Name)
+				if err != nil {
+					t.Error(err)
+				}
+				_, err = watch.Next()
+				if err != nil {
+					t.Error(err)
+				}
+			},
+			deferFunc: func(t *testing.T) {
+				err := r.Deregister(context.Background(), instance1)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
+		{
+			name:   "can't get any",
+			fields: fields{r},
+			args: args{
+				ctx:         context.Background(),
+				serviceName: "server-x",
+			},
+			want:    nil,
+			wantErr: true,
+			preFunc: func(t *testing.T) {
+				if err := r.Register(context.Background(), instance1); err != nil {
+					t.Error(err)
+				}
+				watch, err := r.Watch(context.Background(), instance1.Name)
+				if err != nil {
+					t.Error(err)
+				}
+				_, err = watch.Next()
+				if err != nil {
+					t.Error(err)
+				}
+			},
+			deferFunc: func(t *testing.T) {
+				err := r.Deregister(context.Background(), instance1)
+				if err != nil {
+					t.Error(err)
+				}
+			},
+		},
 	}
 
-	services, err := w.Next()
-	if err != nil {
-		t.Errorf("Next failed: %v", err)
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if test.preFunc != nil {
+				test.preFunc(t)
+			}
+			if test.deferFunc != nil {
+				defer test.deferFunc(t)
+			}
+
+			service, err := test.fields.registry.GetService(context.Background(), test.args.serviceName)
+			if (err != nil) != test.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, test.wantErr)
+				t.Errorf("GetService() got = %v", service)
+				return
+			}
+			if !reflect.DeepEqual(service, test.want) {
+				t.Errorf("GetService() got = %v, want %v", service, test.want)
+			}
+		})
 	}
-	if !reflect.DeepEqual(1, len(services)) {
-		t.Errorf("no expect float_key value: %v, but got: %v", len(services), 1)
+}
+
+func TestRegistry_Watch(t *testing.T) {
+	addr := fmt.Sprintf("%s:9091", getIntranetIP())
+
+	time.Sleep(time.Millisecond * 100)
+	cli, err := api.NewClient(&api.Config{Address: "127.0.0.1:8500", WaitTime: 2 * time.Second})
+	if err != nil {
+		t.Fatalf("create consul client failed: %v", err)
 	}
-	if !reflect.DeepEqual("test2233", services[0].ID) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].ID, "test2233")
+
+	instance1 := &registry.ServiceInstance{
+		ID:        "1",
+		Name:      "server-1",
+		Version:   "v0.0.1",
+		Endpoints: []string{fmt.Sprintf("tcp://%s?isSecure=false", addr)},
 	}
-	if !reflect.DeepEqual("test-provider", services[0].Name) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].Name, "test-provider")
+
+	type args struct {
+		ctx      context.Context
+		opts     []Option
+		instance *registry.ServiceInstance
 	}
-	if !reflect.DeepEqual(version, services[0].Version) {
-		t.Errorf("no expect float_key value: %v, but got: %v", services[0].Version, version)
+	tests := []struct {
+		name    string
+		args    args
+		want    []*registry.ServiceInstance
+		wantErr bool
+		preFunc func(t *testing.T)
+	}{
+		{
+			name: "normal",
+			args: args{
+				ctx:      context.Background(),
+				instance: instance1,
+				opts: []Option{
+					WithHealthCheck(false),
+				},
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+			},
+		},
+		{
+			name: "register with healthCheck",
+			args: args{
+				ctx:      context.Background(),
+				instance: instance1,
+				opts: []Option{
+					WithHeartbeat(true),
+					WithHealthCheck(true),
+					WithHealthCheckInterval(5),
+				},
+			},
+			want:    []*registry.ServiceInstance{instance1},
+			wantErr: false,
+			preFunc: func(t *testing.T) {
+				lis, err := net.Listen("tcp", addr)
+				if err != nil {
+					t.Errorf("listen tcp %s failed!", addr)
+				}
+				go tcpServer(t, lis)
+			},
+		},
 	}
-	err = r.Deregister(ctx, svc)
-	if err != nil {
-		t.Errorf("Deregister failed: %v", err)
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if tt.preFunc != nil {
+				tt.preFunc(t)
+			}
+
+			r := New(cli, tt.args.opts...)
+
+			err := r.Register(tt.args.ctx, tt.args.instance)
+			if err != nil {
+				t.Error(err)
+			}
+			defer func() {
+				err = r.Deregister(tt.args.ctx, tt.args.instance)
+				if err != nil {
+					t.Error(err)
+				}
+			}()
+
+			watch, err := r.Watch(tt.args.ctx, tt.args.instance.Name)
+			if err != nil {
+				t.Error(err)
+			}
+
+			service, err := watch.Next()
+
+			if (err != nil) != tt.wantErr {
+				t.Errorf("GetService() error = %v, wantErr %v", err, tt.wantErr)
+				t.Errorf("GetService() got = %v", service)
+				return
+			}
+			if !reflect.DeepEqual(service, tt.want) {
+				t.Errorf("GetService() got = %v, want %v", service, tt.want)
+			}
+		})
 	}
 }