-
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
490 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
end_of_line = lf | ||
indent_size = 4 | ||
insert_final_newline = true | ||
max_line_length = 160 | ||
tab_width = 4 | ||
trim_trailing_whitespace = true | ||
|
||
[Makefile] | ||
indent_style = space | ||
|
||
[*.feature] | ||
indent_style = space |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ jobs: | |
uses: golangci/[email protected] | ||
with: | ||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. | ||
version: v1.42.0 | ||
version: v1.42.1 | ||
|
||
# Optional: working directory, useful for monorepos | ||
# working-directory: somedir | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ linters: | |
- forcetypeassert | ||
- gci | ||
- gochecknoglobals | ||
- golint | ||
- gomnd | ||
- ifshort | ||
- interfacer | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package grpcmock | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net" | ||
"net/url" | ||
"strings" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/metadata" | ||
"google.golang.org/grpc/test/bufconn" | ||
) | ||
|
||
// ContextDialer is to set up the dialer. | ||
type ContextDialer = func(context.Context, string) (net.Conn, error) | ||
|
||
type invokeConfig struct { | ||
header map[string]string | ||
dialOpts []grpc.DialOption | ||
callOpts []grpc.CallOption | ||
} | ||
|
||
// InvokeOption sets invoker config. | ||
type InvokeOption func(c *invokeConfig) | ||
|
||
// InvokeUnary invokes a unary method. | ||
func InvokeUnary( | ||
ctx context.Context, | ||
method string, | ||
in interface{}, | ||
out interface{}, | ||
opts ...InvokeOption, | ||
) error { | ||
addr, method, err := parseMethod(method) | ||
if err != nil { | ||
return fmt.Errorf("coulld not parse method url: %w", err) | ||
} | ||
|
||
ctx, dialOpts, callOpts := invokeOptions(ctx, opts...) | ||
|
||
conn, err := grpc.DialContext(ctx, addr, dialOpts...) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return conn.Invoke(ctx, method, in, out, callOpts...) | ||
} | ||
|
||
func parseMethod(method string) (string, string, error) { | ||
u, err := url.Parse(method) | ||
if err != nil { | ||
return "", "", err | ||
} | ||
|
||
method = fmt.Sprintf("/%s", strings.TrimLeft(u.Path, "/")) | ||
|
||
if method == "/" { | ||
return "", "", ErrMissingMethod | ||
} | ||
|
||
addr := url.URL{ | ||
Scheme: u.Scheme, | ||
User: u.User, | ||
Host: u.Host, | ||
} | ||
|
||
return addr.String(), method, nil | ||
} | ||
|
||
func invokeOptions(ctx context.Context, opts ...InvokeOption) (context.Context, []grpc.DialOption, []grpc.CallOption) { | ||
cfg := invokeConfig{ | ||
header: map[string]string{}, | ||
} | ||
|
||
for _, o := range opts { | ||
o(&cfg) | ||
} | ||
|
||
if len(cfg.header) > 0 { | ||
ctx = metadata.NewOutgoingContext(ctx, metadata.New(cfg.header)) | ||
} | ||
|
||
return ctx, cfg.dialOpts, cfg.callOpts | ||
} | ||
|
||
// WithHeader sets request header. | ||
func WithHeader(key, value string) InvokeOption { | ||
return func(c *invokeConfig) { | ||
c.header[key] = value | ||
} | ||
} | ||
|
||
// WithHeaders sets request header. | ||
func WithHeaders(header map[string]string) InvokeOption { | ||
return func(c *invokeConfig) { | ||
for k, v := range header { | ||
c.header[k] = v | ||
} | ||
} | ||
} | ||
|
||
// WithContextDialer sets a context dialer to create connections. | ||
// | ||
// See: | ||
// - grpcmock.WithBufConnDialer() | ||
func WithContextDialer(d ContextDialer) InvokeOption { | ||
return WithDialOptions(grpc.WithContextDialer(d)) | ||
} | ||
|
||
// WithBufConnDialer sets a *bufconn.Listener as the context dialer. | ||
// | ||
// See: | ||
// - grpcmock.WithContextDialer() | ||
func WithBufConnDialer(l *bufconn.Listener) InvokeOption { | ||
return WithContextDialer(func(context.Context, string) (net.Conn, error) { | ||
return l.Dial() | ||
}) | ||
} | ||
|
||
// WithInsecure disables transport security for the connections. | ||
func WithInsecure() InvokeOption { | ||
return WithDialOptions(grpc.WithInsecure()) | ||
} | ||
|
||
// WithDialOptions sets dial options. | ||
func WithDialOptions(opts ...grpc.DialOption) InvokeOption { | ||
return func(c *invokeConfig) { | ||
c.dialOpts = append(c.dialOpts, opts...) | ||
} | ||
} | ||
|
||
// WithCallOption sets call options. | ||
func WithCallOption(opts ...grpc.CallOption) InvokeOption { | ||
return func(c *invokeConfig) { | ||
c.callOpts = append(c.callOpts, opts...) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package grpcmock | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestParseMethod(t *testing.T) { | ||
t.Parallel() | ||
|
||
testCases := []struct { | ||
scenario string | ||
method string | ||
expectedAddr string | ||
expectedMethod string | ||
expectedError string | ||
}{ | ||
{ | ||
scenario: "method is not valid", | ||
method: "://", | ||
expectedError: `parse "://": missing protocol scheme`, | ||
}, | ||
{ | ||
scenario: "method is missing", | ||
method: "tcp://:8000/", | ||
expectedError: `missing method`, | ||
}, | ||
{ | ||
scenario: "full url", | ||
method: "tcp://127.0.0.1:8000/server/GetItem", | ||
expectedAddr: "tcp://127.0.0.1:8000", | ||
expectedMethod: "/server/GetItem", | ||
}, | ||
{ | ||
scenario: "method only", | ||
method: "/server/GetItem", | ||
expectedMethod: "/server/GetItem", | ||
}, | ||
{ | ||
scenario: "relative method", | ||
method: "server/GetItem", | ||
expectedMethod: "/server/GetItem", | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
tc := tc | ||
t.Run(tc.scenario, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
addr, method, err := parseMethod(tc.method) | ||
|
||
assert.Equal(t, tc.expectedAddr, addr) | ||
assert.Equal(t, tc.expectedMethod, method) | ||
|
||
if tc.expectedError == "" { | ||
assert.NoError(t, err) | ||
} else { | ||
assert.EqualError(t, err, tc.expectedError) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package grpcmock_test | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"net" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/test/bufconn" | ||
|
||
"github.com/nhatthm/grpcmock" | ||
) | ||
|
||
func TestInvokeUnary_MethodError(t *testing.T) { | ||
t.Parallel() | ||
|
||
err := grpcmock.InvokeUnary(context.Background(), "://", nil, nil) | ||
expected := `coulld not parse method url: parse "://": missing protocol scheme` | ||
|
||
assert.EqualError(t, err, expected) | ||
} | ||
|
||
func TestInvokeUnary_DialError(t *testing.T) { | ||
t.Parallel() | ||
|
||
dialer := func(context.Context, string) (net.Conn, error) { | ||
return nil, errors.New("dial error") | ||
} | ||
|
||
err := grpcmock.InvokeUnary(context.Background(), "NotFound", nil, nil, | ||
grpcmock.WithContextDialer(dialer), | ||
grpcmock.WithInsecure(), | ||
) | ||
expected := `rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing dial error"` | ||
|
||
assert.EqualError(t, err, expected) | ||
} | ||
|
||
func TestInvokeUnary_Unimplemented(t *testing.T) { | ||
t.Parallel() | ||
|
||
l := bufconn.Listen(1024 * 1024) | ||
|
||
srv := grpc.NewServer() | ||
defer srv.Stop() | ||
|
||
go func() { | ||
_ = srv.Serve(l) // nolint: errcheck | ||
}() | ||
|
||
err := grpcmock.InvokeUnary(context.Background(), "grpctest/GetItem", nil, nil, | ||
grpcmock.WithBufConnDialer(l), | ||
grpcmock.WithInsecure(), | ||
) | ||
expected := `rpc error: code = Unimplemented desc = unknown service grpctest` | ||
|
||
assert.EqualError(t, err, expected) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
// Package main | ||
package main | ||
// Package grpcmock provides functionalities for testing grpc client and server. | ||
package grpcmock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package grpcmock | ||
|
||
const ( | ||
// ErrMissingMethod indicates that there is no method in the url. | ||
ErrMissingMethod err = "missing method" | ||
) | ||
|
||
type err string | ||
|
||
// Error returns the error string. | ||
func (e err) Error() string { | ||
return string(e) | ||
} |
Oops, something went wrong.