adds tests
This commit is contained in:
parent
d76351d11b
commit
c7a5192cf6
|
|
@ -0,0 +1,303 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/jsonpb"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
"github.com/golang/protobuf/ptypes/struct"
|
||||||
|
"github.com/jhump/protoreflect/desc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
|
||||||
|
"github.com/fullstorydev/grpcurl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRequestFactory(t *testing.T) {
|
||||||
|
source, err := grpcurl.DescriptorSourceFromProtoSets("../../testing/example.protoset")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create descriptor source: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := makeProto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
format string
|
||||||
|
input string
|
||||||
|
expectedOutput []proto.Message
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
format: "json",
|
||||||
|
input: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "json",
|
||||||
|
input: messageAsJSON,
|
||||||
|
expectedOutput: []proto.Message{msg},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "json",
|
||||||
|
input: messageAsJSON + messageAsJSON + messageAsJSON,
|
||||||
|
expectedOutput: []proto.Message{msg, msg, msg},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// unlike JSON, empty input yields one empty message (vs. zero messages)
|
||||||
|
format: "text",
|
||||||
|
input: "",
|
||||||
|
expectedOutput: []proto.Message{&structpb.Value{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "text",
|
||||||
|
input: messageAsText,
|
||||||
|
expectedOutput: []proto.Message{msg},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "text",
|
||||||
|
input: messageAsText + string(textSeparatorChar),
|
||||||
|
expectedOutput: []proto.Message{msg, &structpb.Value{}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: "text",
|
||||||
|
input: messageAsText + string(textSeparatorChar) + messageAsText + string(textSeparatorChar) + messageAsText,
|
||||||
|
expectedOutput: []proto.Message{msg, msg, msg},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range testCases {
|
||||||
|
name := fmt.Sprintf("#%d, %s, %d message(s)", i+1, tc.format, len(tc.expectedOutput))
|
||||||
|
rf, _ := formatDetails(tc.format, source, false, strings.NewReader(tc.input))
|
||||||
|
numReqs := 0
|
||||||
|
for {
|
||||||
|
var req structpb.Value
|
||||||
|
err := rf.next(&req)
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
t.Errorf("%s, msg %d: unexpected error: %v", name, numReqs, err)
|
||||||
|
}
|
||||||
|
if !proto.Equal(&req, tc.expectedOutput[numReqs]) {
|
||||||
|
t.Errorf("%s, msg %d: incorrect message;\nexpecting:\n%v\ngot:\n%v", name, numReqs, tc.expectedOutput[numReqs], &req)
|
||||||
|
}
|
||||||
|
numReqs++
|
||||||
|
}
|
||||||
|
if rf.numRequests() != numReqs {
|
||||||
|
t.Errorf("%s: factory reported wrong number of requests: expecting %d, got %d", name, numReqs, rf.numRequests())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler prints response data (and headers/trailers in verbose mode).
|
||||||
|
// This verifies that we get the right output in both JSON and proto text modes.
|
||||||
|
func TestHandler(t *testing.T) {
|
||||||
|
source, err := grpcurl.DescriptorSourceFromProtoSets("../../testing/example.protoset")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create descriptor source: %v", err)
|
||||||
|
}
|
||||||
|
d, err := source.FindSymbol("TestService.GetFiles")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to find method 'TestService.GetFiles': %v", err)
|
||||||
|
}
|
||||||
|
md, ok := d.(*desc.MethodDescriptor)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("wrong kind of descriptor found: %T", d)
|
||||||
|
}
|
||||||
|
|
||||||
|
reqHeaders := metadata.Pairs("foo", "123", "bar", "456")
|
||||||
|
respHeaders := metadata.Pairs("foo", "abc", "bar", "def", "baz", "xyz")
|
||||||
|
respTrailers := metadata.Pairs("a", "1", "b", "2", "c", "3")
|
||||||
|
rsp, err := makeProto()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create response message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range []string{"json", "text"} {
|
||||||
|
for _, numMessages := range []int{1, 3} {
|
||||||
|
for _, verbose := range []bool{true, false} {
|
||||||
|
name := fmt.Sprintf("%s, %d message(s)", format, numMessages)
|
||||||
|
if verbose {
|
||||||
|
name += ", verbose"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, formatter := formatDetails(format, source, verbose, nil)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
h := handler{
|
||||||
|
out: &buf,
|
||||||
|
descSource: source,
|
||||||
|
verbose: verbose,
|
||||||
|
formatter: formatter,
|
||||||
|
}
|
||||||
|
|
||||||
|
h.OnResolveMethod(md)
|
||||||
|
h.OnSendHeaders(reqHeaders)
|
||||||
|
h.OnReceiveHeaders(respHeaders)
|
||||||
|
for i := 0; i < numMessages; i++ {
|
||||||
|
h.OnReceiveResponse(rsp)
|
||||||
|
}
|
||||||
|
h.OnReceiveTrailers(nil, respTrailers)
|
||||||
|
|
||||||
|
expectedOutput := ""
|
||||||
|
if verbose {
|
||||||
|
expectedOutput += verbosePrefix
|
||||||
|
}
|
||||||
|
for i := 0; i < numMessages; i++ {
|
||||||
|
if verbose {
|
||||||
|
expectedOutput += verboseResponseHeader
|
||||||
|
}
|
||||||
|
if format == "json" {
|
||||||
|
expectedOutput += messageAsJSON
|
||||||
|
} else {
|
||||||
|
if i > 0 && !verbose {
|
||||||
|
expectedOutput += string(textSeparatorChar)
|
||||||
|
}
|
||||||
|
expectedOutput += messageAsText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
expectedOutput += verboseSuffix
|
||||||
|
}
|
||||||
|
|
||||||
|
out := buf.String()
|
||||||
|
if !compare(out, expectedOutput) {
|
||||||
|
t.Errorf("%s: Incorrect output.", name) // Expected:\n%s\nGot:\n%s", name, expectedOutput, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare checks that actual and expected are equal, returning true if so.
|
||||||
|
// A simple equality check (==) does not suffice because jsonpb formats
|
||||||
|
// structpb.Value strangely. So if that formatting gets fixed, we don't
|
||||||
|
// want this test in grpcurl to suddenly start failing. So we check each
|
||||||
|
// line and compare the lines after stripping whitespace (which removes
|
||||||
|
// the jsonpb format anomalies).
|
||||||
|
func compare(actual, expected string) bool {
|
||||||
|
actualLines := strings.Split(actual, "\n")
|
||||||
|
expectedLines := strings.Split(expected, "\n")
|
||||||
|
if len(actualLines) != len(expectedLines) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(actualLines); i++ {
|
||||||
|
if strings.TrimSpace(actualLines[i]) != strings.TrimSpace(expectedLines[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeProto() (proto.Message, error) {
|
||||||
|
var rsp structpb.Value
|
||||||
|
err := jsonpb.UnmarshalString(`{
|
||||||
|
"foo": ["abc", "def", "ghi"],
|
||||||
|
"bar": { "a": 1, "b": 2 },
|
||||||
|
"baz": true,
|
||||||
|
"null": null
|
||||||
|
}`, &rsp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &rsp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
verbosePrefix = `
|
||||||
|
Resolved method descriptor:
|
||||||
|
{
|
||||||
|
"name": "GetFiles",
|
||||||
|
"inputType": ".TestRequest",
|
||||||
|
"outputType": ".TestResponse",
|
||||||
|
"options": {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Request metadata to send:
|
||||||
|
bar: 456
|
||||||
|
foo: 123
|
||||||
|
|
||||||
|
Response headers received:
|
||||||
|
bar: def
|
||||||
|
baz: xyz
|
||||||
|
foo: abc
|
||||||
|
`
|
||||||
|
verboseSuffix = `
|
||||||
|
Response trailers received:
|
||||||
|
a: 1
|
||||||
|
b: 2
|
||||||
|
c: 3
|
||||||
|
`
|
||||||
|
verboseResponseHeader = `
|
||||||
|
Response contents:
|
||||||
|
`
|
||||||
|
messageAsJSON = `{
|
||||||
|
"bar": {
|
||||||
|
"a": 1,
|
||||||
|
"b": 2
|
||||||
|
},
|
||||||
|
"baz": true,
|
||||||
|
"foo": [
|
||||||
|
"abc",
|
||||||
|
"def",
|
||||||
|
"ghi"
|
||||||
|
],
|
||||||
|
"null": null
|
||||||
|
}
|
||||||
|
`
|
||||||
|
messageAsText = `struct_value: <
|
||||||
|
fields: <
|
||||||
|
key: "bar"
|
||||||
|
value: <
|
||||||
|
struct_value: <
|
||||||
|
fields: <
|
||||||
|
key: "a"
|
||||||
|
value: <
|
||||||
|
number_value: 1
|
||||||
|
>
|
||||||
|
>
|
||||||
|
fields: <
|
||||||
|
key: "b"
|
||||||
|
value: <
|
||||||
|
number_value: 2
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
fields: <
|
||||||
|
key: "baz"
|
||||||
|
value: <
|
||||||
|
bool_value: true
|
||||||
|
>
|
||||||
|
>
|
||||||
|
fields: <
|
||||||
|
key: "foo"
|
||||||
|
value: <
|
||||||
|
list_value: <
|
||||||
|
values: <
|
||||||
|
string_value: "abc"
|
||||||
|
>
|
||||||
|
values: <
|
||||||
|
string_value: "def"
|
||||||
|
>
|
||||||
|
values: <
|
||||||
|
string_value: "ghi"
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
fields: <
|
||||||
|
key: "null"
|
||||||
|
value: <
|
||||||
|
null_value: NULL_VALUE
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
@ -65,21 +65,22 @@ var (
|
||||||
rpcHeaders multiString
|
rpcHeaders multiString
|
||||||
reflHeaders multiString
|
reflHeaders multiString
|
||||||
authority = flag.String("authority", "",
|
authority = flag.String("authority", "",
|
||||||
":authority pseudo header value to be passed along with underlying HTTP/2 requests. It defaults to `host [ \":\" port ]` part of the target url.")
|
`:authority pseudo header value to be passed along with underlying HTTP/2
|
||||||
|
requests. It defaults to 'host [ ":" port ]' part of the target url.`)
|
||||||
data = flag.String("d", "",
|
data = flag.String("d", "",
|
||||||
`Data for request contents. If the value is '@' then the request contents
|
`Data for request contents. If the value is '@' then the request contents
|
||||||
are read from stdin. For calls that accept a stream of requests, the
|
are read from stdin. For calls that accept a stream of requests, the
|
||||||
contents should include all such request messages concatenated together
|
contents should include all such request messages concatenated together
|
||||||
(optionally separated by whitespace).`)
|
(optionally separated by whitespace).`)
|
||||||
format = flag.String("format", "",
|
format = flag.String("format", "json",
|
||||||
`The format of request data. The allowed values are 'json' (the default)
|
`The format of request data. The allowed values are 'json' or 'text'. For
|
||||||
or 'text'. For 'json', the input data must be in JSON format. Multiple
|
'json', the input data must be in JSON format. Multiple request values may
|
||||||
request values may be concatenated (messages with a JSON representation
|
be concatenated (messages with a JSON representation other than object
|
||||||
other than Object must be separated by whitespace, such as a newline). For
|
must be separated by whitespace, such as a newline). For 'text', the input
|
||||||
'text', the input data must be in the protobuf text format, in which case
|
data must be in the protobuf text format, in which case multiple request
|
||||||
multiple request values must be separated by the "record separate" ASCII
|
values must be separated by the "record separate" ASCII character: 0x1E.
|
||||||
character: 0x1E. The stream should not end in a record separator. If it
|
The stream should not end in a record separator. If it does, it will be
|
||||||
does, it will be interpreted as a final, blank message after the separator.`)
|
interpreted as a final, blank message after the separator.`)
|
||||||
connectTimeout = flag.String("connect-timeout", "",
|
connectTimeout = flag.String("connect-timeout", "",
|
||||||
`The maximum time, in seconds, to wait for connection to be established.
|
`The maximum time, in seconds, to wait for connection to be established.
|
||||||
Defaults to 10 seconds.`)
|
Defaults to 10 seconds.`)
|
||||||
|
|
@ -180,7 +181,7 @@ func main() {
|
||||||
if (*key == "") != (*cert == "") {
|
if (*key == "") != (*cert == "") {
|
||||||
fail(nil, "The -cert and -key arguments must be used together and both be present.")
|
fail(nil, "The -cert and -key arguments must be used together and both be present.")
|
||||||
}
|
}
|
||||||
if *format != "" && *format != "json" && *format != "text" {
|
if *format != "json" && *format != "text" {
|
||||||
fail(nil, "The -format option must be 'json' or 'text.")
|
fail(nil, "The -format option must be 'json' or 'text.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,7 +433,7 @@ func main() {
|
||||||
// create a request to invoke an RPC
|
// create a request to invoke an RPC
|
||||||
tmpl := makeTemplate(dynamic.NewMessage(dsc))
|
tmpl := makeTemplate(dynamic.NewMessage(dsc))
|
||||||
fmt.Println("\nMessage template:")
|
fmt.Println("\nMessage template:")
|
||||||
if *format == "" || *format == "json" {
|
if *format == "json" {
|
||||||
jsm := jsonpb.Marshaler{Indent: " ", EmitDefaults: true}
|
jsm := jsonpb.Marshaler{Indent: " ", EmitDefaults: true}
|
||||||
err := jsm.Marshal(os.Stdout, tmpl)
|
err := jsm.Marshal(os.Stdout, tmpl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -460,24 +461,12 @@ func main() {
|
||||||
in = strings.NewReader(*data)
|
in = strings.NewReader(*data)
|
||||||
}
|
}
|
||||||
|
|
||||||
var rf requestFactory
|
rf, formatter := formatDetails(*format, descSource, *verbose, in)
|
||||||
var h handler
|
h := handler{
|
||||||
if *format == "" || *format == "json" {
|
out: os.Stdout,
|
||||||
resolver, err := anyResolver(descSource)
|
descSource: descSource,
|
||||||
if err != nil {
|
formatter: formatter,
|
||||||
fail(err, "Error creating message resolver")
|
verbose: *verbose,
|
||||||
}
|
|
||||||
rf = newJsonFactory(in, resolver)
|
|
||||||
h = handler{
|
|
||||||
descSource: descSource,
|
|
||||||
marshaler: jsonpb.Marshaler{
|
|
||||||
EmitDefaults: *emitDefaults,
|
|
||||||
AnyResolver: resolver,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rf = newTextFactory(in)
|
|
||||||
h = handler{descSource: descSource}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err := grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), &h, rf.next)
|
err := grpcurl.InvokeRPC(ctx, descSource, cc, symbol, append(addlHeaders, rpcHeaders...), &h, rf.next)
|
||||||
|
|
@ -568,63 +557,74 @@ func anyResolver(source grpcurl.DescriptorSource) (jsonpb.AnyResolver, error) {
|
||||||
return dynamic.AnyResolver(mf, files...), nil
|
return dynamic.AnyResolver(mf, files...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatDetails(format string, descSource grpcurl.DescriptorSource, verbose bool, in io.Reader) (requestFactory, func(proto.Message) (string, error)) {
|
||||||
|
if format == "json" {
|
||||||
|
resolver, err := anyResolver(descSource)
|
||||||
|
if err != nil {
|
||||||
|
fail(err, "Error creating message resolver")
|
||||||
|
}
|
||||||
|
marshaler := jsonpb.Marshaler{
|
||||||
|
EmitDefaults: *emitDefaults,
|
||||||
|
Indent: " ",
|
||||||
|
AnyResolver: resolver,
|
||||||
|
}
|
||||||
|
return newJsonFactory(in, resolver), marshaler.MarshalToString
|
||||||
|
}
|
||||||
|
/* else *format == "text" */
|
||||||
|
|
||||||
|
// if not verbose output, then also include record delimiters
|
||||||
|
// before each message (other than the first) so output could
|
||||||
|
// potentially piped to another grpcurl process
|
||||||
|
tf := textFormatter{useSeparator: !verbose}
|
||||||
|
return newTextFactory(in), tf.format
|
||||||
|
}
|
||||||
|
|
||||||
type handler struct {
|
type handler struct {
|
||||||
|
out io.Writer
|
||||||
descSource grpcurl.DescriptorSource
|
descSource grpcurl.DescriptorSource
|
||||||
respCount int
|
respCount int
|
||||||
stat *status.Status
|
stat *status.Status
|
||||||
marshaler jsonpb.Marshaler
|
formatter func(proto.Message) (string, error)
|
||||||
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) {
|
func (h *handler) OnResolveMethod(md *desc.MethodDescriptor) {
|
||||||
if *verbose {
|
if h.verbose {
|
||||||
txt, err := grpcurl.GetDescriptorText(md, h.descSource)
|
txt, err := grpcurl.GetDescriptorText(md, h.descSource)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Printf("\nResolved method descriptor:\n%s\n", txt)
|
fmt.Fprintf(h.out, "\nResolved method descriptor:\n%s\n", txt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*handler) OnSendHeaders(md metadata.MD) {
|
func (h *handler) OnSendHeaders(md metadata.MD) {
|
||||||
if *verbose {
|
if h.verbose {
|
||||||
fmt.Printf("\nRequest metadata to send:\n%s\n", grpcurl.MetadataToString(md))
|
fmt.Fprintf(h.out, "\nRequest metadata to send:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*handler) OnReceiveHeaders(md metadata.MD) {
|
func (h *handler) OnReceiveHeaders(md metadata.MD) {
|
||||||
if *verbose {
|
if h.verbose {
|
||||||
fmt.Printf("\nResponse headers received:\n%s\n", grpcurl.MetadataToString(md))
|
fmt.Fprintf(h.out, "\nResponse headers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rs = string(0x1e)
|
|
||||||
|
|
||||||
func (h *handler) OnReceiveResponse(resp proto.Message) {
|
func (h *handler) OnReceiveResponse(resp proto.Message) {
|
||||||
h.respCount++
|
h.respCount++
|
||||||
if *verbose {
|
if h.verbose {
|
||||||
fmt.Print("\nResponse contents:\n")
|
fmt.Fprint(h.out, "\nResponse contents:\n")
|
||||||
}
|
|
||||||
var respStr string
|
|
||||||
var err error
|
|
||||||
if *format == "" || *format == "json" {
|
|
||||||
respStr, err = h.marshaler.MarshalToString(resp)
|
|
||||||
} else /* *format == "text" */ {
|
|
||||||
respStr = proto.MarshalTextString(resp)
|
|
||||||
if !*verbose {
|
|
||||||
// if not verbose output, then also include record delimiters,
|
|
||||||
// so output could potentially piped to another grpcurl process
|
|
||||||
respStr = respStr + rs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
respStr, err := h.formatter(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err, "failed to generate JSON form of response message")
|
fail(err, "failed to generate JSON form of response message")
|
||||||
}
|
}
|
||||||
fmt.Println(respStr)
|
fmt.Fprintln(h.out, respStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
func (h *handler) OnReceiveTrailers(stat *status.Status, md metadata.MD) {
|
||||||
h.stat = stat
|
h.stat = stat
|
||||||
if *verbose {
|
if h.verbose {
|
||||||
fmt.Printf("\nResponse trailers received:\n%s\n", grpcurl.MetadataToString(md))
|
fmt.Fprintf(h.out, "\nResponse trailers received:\n%s\n", grpcurl.MetadataToString(md))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -720,6 +720,10 @@ func (f *jsonFactory) numRequests() int {
|
||||||
return f.requestCount
|
return f.requestCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
textSeparatorChar = 0x1e
|
||||||
|
)
|
||||||
|
|
||||||
type textFactory struct {
|
type textFactory struct {
|
||||||
r *bufio.Reader
|
r *bufio.Reader
|
||||||
err error
|
err error
|
||||||
|
|
@ -736,18 +740,47 @@ func (f *textFactory) next(m proto.Message) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var b []byte
|
var b []byte
|
||||||
b, f.err = f.r.ReadBytes(0x1e)
|
b, f.err = f.r.ReadBytes(textSeparatorChar)
|
||||||
if f.err != nil && f.err != io.EOF {
|
if f.err != nil && f.err != io.EOF {
|
||||||
return f.err
|
return f.err
|
||||||
}
|
}
|
||||||
// remove delimiter
|
// remove delimiter
|
||||||
if b[len(b)-1] == 0x1e {
|
if len(b) > 0 && b[len(b)-1] == textSeparatorChar {
|
||||||
b = b[:len(b)-1]
|
b = b[:len(b)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f.requestCount++
|
||||||
|
|
||||||
return proto.UnmarshalText(string(b), m)
|
return proto.UnmarshalText(string(b), m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *textFactory) numRequests() int {
|
func (f *textFactory) numRequests() int {
|
||||||
return f.requestCount
|
return f.requestCount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type textFormatter struct {
|
||||||
|
useSeparator bool
|
||||||
|
numFormatted int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tf *textFormatter) format(m proto.Message) (string, error) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if tf.useSeparator && tf.numFormatted > 0 {
|
||||||
|
if err := buf.WriteByte(textSeparatorChar); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := proto.MarshalText(&buf, m); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// no trailing newline needed
|
||||||
|
str := buf.String()
|
||||||
|
if str[len(str)-1] == '\n' {
|
||||||
|
str = str[:len(str)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
tf.numFormatted++
|
||||||
|
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
17
grpcurl.go
17
grpcurl.go
|
|
@ -783,16 +783,29 @@ func MetadataToString(md metadata.MD) string {
|
||||||
if len(md) == 0 {
|
if len(md) == 0 {
|
||||||
return "(empty)"
|
return "(empty)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(md))
|
||||||
|
for k := range md {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
for k, vs := range md {
|
first := true
|
||||||
|
for _, k := range keys {
|
||||||
|
vs := md[k]
|
||||||
for _, v := range vs {
|
for _, v := range vs {
|
||||||
|
if first {
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
b.WriteString(k)
|
b.WriteString(k)
|
||||||
b.WriteString(": ")
|
b.WriteString(": ")
|
||||||
if strings.HasSuffix(k, "-bin") {
|
if strings.HasSuffix(k, "-bin") {
|
||||||
v = base64.StdEncoding.EncodeToString([]byte(v))
|
v = base64.StdEncoding.EncodeToString([]byte(v))
|
||||||
}
|
}
|
||||||
b.WriteString(v)
|
b.WriteString(v)
|
||||||
b.WriteString("\n")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return b.String()
|
return b.String()
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,10 @@ type descSourceCase struct {
|
||||||
includeRefl bool
|
includeRefl bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NB: These tests intentionally use the deprecated InvokeRpc since that
|
||||||
|
// calls the other (non-deprecated InvokeRPC). That allows the tests to
|
||||||
|
// easily exercise both functions.
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
var err error
|
var err error
|
||||||
sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset")
|
sourceProtoset, err = DescriptorSourceFromProtoSets("testing/test.protoset")
|
||||||
|
|
@ -235,6 +239,73 @@ func doTestListMethods(t *testing.T, source DescriptorSource, includeReflection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetAllFiles(t *testing.T) {
|
||||||
|
expectedFiles := []string{"testing/test.proto"}
|
||||||
|
// server reflection picks up filename from linked in Go package,
|
||||||
|
// which indicates "grpc_testing/test.proto", not our local copy.
|
||||||
|
expectedFilesWithReflection := []string{"grpc_reflection_v1alpha/reflection.proto", "grpc_testing/test.proto"}
|
||||||
|
|
||||||
|
for _, ds := range descSources {
|
||||||
|
t.Run(ds.name, func(t *testing.T) {
|
||||||
|
files, err := GetAllFiles(ds.source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get all files: %v", err)
|
||||||
|
}
|
||||||
|
names := fileNames(files)
|
||||||
|
expected := expectedFiles
|
||||||
|
if ds.includeRefl {
|
||||||
|
expected = expectedFilesWithReflection
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(expected, names) {
|
||||||
|
t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expected, names)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// try cases with more complicated set of files
|
||||||
|
otherSourceProtoset, err := DescriptorSourceFromProtoSets("testing/test.protoset", "testing/example.protoset")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
otherSourceProtoFiles, err := DescriptorSourceFromProtoFiles(nil, "testing/test.proto", "testing/example.proto")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err.Error())
|
||||||
|
}
|
||||||
|
otherDescSources := []descSourceCase{
|
||||||
|
{"protoset[b]", otherSourceProtoset, false},
|
||||||
|
{"proto[b]", otherSourceProtoFiles, false},
|
||||||
|
}
|
||||||
|
expectedFiles = []string{
|
||||||
|
"google/protobuf/any.proto",
|
||||||
|
"google/protobuf/descriptor.proto",
|
||||||
|
"google/protobuf/empty.proto",
|
||||||
|
"google/protobuf/timestamp.proto",
|
||||||
|
"testing/example.proto",
|
||||||
|
"testing/example2.proto",
|
||||||
|
"testing/test.proto",
|
||||||
|
}
|
||||||
|
for _, ds := range otherDescSources {
|
||||||
|
t.Run(ds.name, func(t *testing.T) {
|
||||||
|
files, err := GetAllFiles(ds.source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to get all files: %v", err)
|
||||||
|
}
|
||||||
|
names := fileNames(files)
|
||||||
|
if !reflect.DeepEqual(expectedFiles, names) {
|
||||||
|
t.Errorf("GetAllFiles returned wrong results: wanted %v, got %v", expectedFiles, names)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileNames(files []*desc.FileDescriptor) []string {
|
||||||
|
names := make([]string, len(files))
|
||||||
|
for i, f := range files {
|
||||||
|
names[i] = f.GetName()
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
func TestDescribe(t *testing.T) {
|
func TestDescribe(t *testing.T) {
|
||||||
for _, ds := range descSources {
|
for _, ds := range descSources {
|
||||||
t.Run(ds.name, func(t *testing.T) {
|
t.Run(ds.name, func(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ cd "$(dirname $0)"
|
||||||
# Run this script to generate files used by tests.
|
# Run this script to generate files used by tests.
|
||||||
|
|
||||||
echo "Creating protosets..."
|
echo "Creating protosets..."
|
||||||
protoc ../../../google.golang.org/grpc/interop/grpc_testing/test.proto \
|
protoc testing/test.proto \
|
||||||
-I../../../ --include_imports \
|
--include_imports \
|
||||||
--descriptor_set_out=testing/test.protoset
|
--descriptor_set_out=testing/test.protoset
|
||||||
|
|
||||||
protoc testing/example.proto \
|
protoc testing/example.proto \
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ syntax = "proto3";
|
||||||
import "google/protobuf/descriptor.proto";
|
import "google/protobuf/descriptor.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "testing/example2.proto";
|
||||||
|
|
||||||
message TestRequest {
|
message TestRequest {
|
||||||
repeated string file_names = 1;
|
repeated string file_names = 1;
|
||||||
|
repeated Extension extensions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TestResponse {
|
message TestResponse {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,8 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "google/protobuf/any.proto";
|
||||||
|
|
||||||
|
message Extension {
|
||||||
|
uint64 id = 1;
|
||||||
|
google.protobuf.Any data = 2;
|
||||||
|
}
|
||||||
Binary file not shown.
Loading…
Reference in New Issue