Repository: bojand/ghz
Pull request #180 (2020-04-21 01:04:50)

Add tests for well known types and documentation


Patch:
diff --git a/go.sum b/go.sum
index 3f9bf3a..6409a9d 100644
--- a/go.sum
+++ b/go.sum
@@ -139,12 +139,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
 github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
 go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4=
-go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
diff --git a/internal/common.go b/internal/common.go
index 01f6f3a..273322b 100644
--- a/internal/common.go
+++ b/internal/common.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/bojand/ghz/internal/helloworld"
 	"github.com/bojand/ghz/internal/sleep"
+	"github.com/bojand/ghz/internal/wrapped"
 )
 
 // TestPort is the port.
@@ -95,3 +96,36 @@ func StartSleepServer(secure bool) (*sleep.SleepService, *grpc.Server, error) {
 
 	return &ss, s, err
 }
+
+// StartWrappedServer starts the wrapped test server
+func StartWrappedServer(secure bool) (*wrapped.WrappedService, *grpc.Server, error) {
+	lis, err := net.Listen("tcp", ":0")
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var opts []grpc.ServerOption
+
+	if secure {
+		creds, err := credentials.NewServerTLSFromFile("../testdata/localhost.crt", "../testdata/localhost.key")
+		if err != nil {
+			return nil, nil, err
+		}
+		opts = []grpc.ServerOption{grpc.Creds(creds)}
+	}
+
+	s := grpc.NewServer(opts...)
+
+	ws := wrapped.WrappedService{}
+	wrapped.RegisterWrappedServiceServer(s, &ws)
+	reflection.Register(s)
+
+	TestPort = strconv.Itoa(lis.Addr().(*net.TCPAddr).Port)
+	TestLocalhost = "localhost:" + TestPort
+
+	go func() {
+		_ = s.Serve(lis)
+	}()
+
+	return &ws, s, err
+}
diff --git a/internal/wrapped/server.go b/internal/wrapped/server.go
new file mode 100644
index 0000000..3ca4527
--- /dev/null
+++ b/internal/wrapped/server.go
@@ -0,0 +1,13 @@
+package wrapped
+
+import (
+	"context"
+
+	wrappers "github.com/golang/protobuf/ptypes/wrappers"
+)
+
+type WrappedService struct{}
+
+func (s *WrappedService) GetMessage(ctx context.Context, req *wrappers.StringValue) (*wrappers.StringValue, error) {
+	return &wrappers.StringValue{Value: "Hello: " + req.GetValue()}, nil
+}
diff --git a/internal/wrapped/wrapped.pb.go b/internal/wrapped/wrapped.pb.go
new file mode 100644
index 0000000..d3eb982
--- /dev/null
+++ b/internal/wrapped/wrapped.pb.go
@@ -0,0 +1,121 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: wrapped.proto
+
+package wrapped
+
+import (
+	context "context"
+	fmt "fmt"
+	proto "github.com/golang/protobuf/proto"
+	wrappers "github.com/golang/protobuf/ptypes/wrappers"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+	math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+func init() { proto.RegisterFile("wrapped.proto", fileDescriptor_a9aa7f80a2b4490d) }
+
+var fileDescriptor_a9aa7f80a2b4490d = []byte{
+	// 135 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2d, 0x2f, 0x4a, 0x2c,
+	0x28, 0x48, 0x4d, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x87, 0x72, 0xa5, 0xe4, 0xd2,
+	0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0xc1, 0xc2, 0x49, 0xa5, 0x69, 0xfa, 0x10, 0x89, 0xa2, 0x62,
+	0x88, 0x42, 0xa3, 0x28, 0x2e, 0xbe, 0x70, 0x88, 0xd2, 0xe0, 0xd4, 0xa2, 0xb2, 0xcc, 0xe4, 0x54,
+	0x21, 0x0f, 0x2e, 0x2e, 0xf7, 0xd4, 0x12, 0xdf, 0xd4, 0xe2, 0xe2, 0xc4, 0xf4, 0x54, 0x21, 0x19,
+	0x3d, 0x88, 0x01, 0x7a, 0x30, 0x03, 0xf4, 0x82, 0x4b, 0x8a, 0x32, 0xf3, 0xd2, 0xc3, 0x12, 0x73,
+	0x4a, 0x53, 0xa5, 0xf0, 0xca, 0x3a, 0x09, 0x45, 0x09, 0x64, 0xe6, 0x95, 0xa4, 0x16, 0xe5, 0x25,
+	0xe6, 0x40, 0xad, 0x4d, 0x49, 0x62, 0x03, 0xab, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xfd,
+	0xf7, 0x3c, 0x4d, 0xb0, 0x00, 0x00, 0x00,
+}
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ context.Context
+var _ grpc.ClientConn
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+const _ = grpc.SupportPackageIsVersion4
+
+// WrappedServiceClient is the client API for WrappedService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
+type WrappedServiceClient interface {
+	GetMessage(ctx context.Context, in *wrappers.StringValue, opts ...grpc.CallOption) (*wrappers.StringValue, error)
+}
+
+type wrappedServiceClient struct {
+	cc *grpc.ClientConn
+}
+
+func NewWrappedServiceClient(cc *grpc.ClientConn) WrappedServiceClient {
+	return &wrappedServiceClient{cc}
+}
+
+func (c *wrappedServiceClient) GetMessage(ctx context.Context, in *wrappers.StringValue, opts ...grpc.CallOption) (*wrappers.StringValue, error) {
+	out := new(wrappers.StringValue)
+	err := c.cc.Invoke(ctx, "/wrapped.WrappedService/GetMessage", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// WrappedServiceServer is the server API for WrappedService service.
+type WrappedServiceServer interface {
+	GetMessage(context.Context, *wrappers.StringValue) (*wrappers.StringValue, error)
+}
+
+// UnimplementedWrappedServiceServer can be embedded to have forward compatible implementations.
+type UnimplementedWrappedServiceServer struct {
+}
+
+func (*UnimplementedWrappedServiceServer) GetMessage(ctx context.Context, req *wrappers.StringValue) (*wrappers.StringValue, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method GetMessage not implemented")
+}
+
+func RegisterWrappedServiceServer(s *grpc.Server, srv WrappedServiceServer) {
+	s.RegisterService(&_WrappedService_serviceDesc, srv)
+}
+
+func _WrappedService_GetMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(wrappers.StringValue)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(WrappedServiceServer).GetMessage(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/wrapped.WrappedService/GetMessage",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(WrappedServiceServer).GetMessage(ctx, req.(*wrappers.StringValue))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+var _WrappedService_serviceDesc = grpc.ServiceDesc{
+	ServiceName: "wrapped.WrappedService",
+	HandlerType: (*WrappedServiceServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "GetMessage",
+			Handler:    _WrappedService_GetMessage_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "wrapped.proto",
+}
diff --git a/www/docs/examples.md b/www/docs/examples.md
index 2962a69..d2b9224 100644
--- a/www/docs/examples.md
+++ b/www/docs/examples.md
@@ -3,7 +3,9 @@ id: examples
 title: Examples
 ---
 
-A simple insecure unary call:
+<AUTOGENERATED_TABLE_OF_CONTENTS>
+
+### A simple insecure unary call:
 
 ```sh
 ghz --insecure \
@@ -13,6 +15,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Server reflection
+
 Or same test using [server reflection](https://github.com/grpc/grpc/blob/master/doc/server-reflection.md) (just omit `-proto` option):
 
 ```sh
@@ -22,6 +26,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Metadata using template variables
+
 A simple unary call with metadata using template actions:
 
 ```sh
@@ -33,6 +39,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Binary data
+
 Using binary data file (see [writing a message](https://developers.google.com/protocol-buffers/docs/gotutorial#writing-a-message)):
 
 ```sh
@@ -50,6 +58,8 @@ ghz --proto ./greeter.proto \
   0.0.0.0:50051 < ./hello_request_data.bin
 ```
 
+### Variable data for unary calls
+
 Round-robin of messages for unary call:
 
 ```sh
@@ -60,6 +70,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Custom parameters
+
 Custom number of requests and concurrency:
 
 ```sh
@@ -94,6 +106,8 @@ ghz --proto ./greeter.proto \
   0.0.0.0:50051
 ```
 
+### Protoset
+
 If a single object is given for data it is sent as every message.
 
 We can also use `.protoset` files which can bundle multiple protocol buffer files into one binary file.
@@ -122,6 +136,8 @@ Alternatively `ghz` can be used with [Prototool](https://github.com/uber/prototo
 ghz --protoset $(prototool descriptor-set --include-imports --tmp) ...
 ```
 
+### Config file
+
 Finally we can specify all settings, including the target host, conveniently in a JSON or TOML config file.
 
 ```sh
@@ -134,6 +150,8 @@ Config file settings can be combined with command line arguments. CLI options ov
 ghz --config ./config.json -c 20 -n 1000
 ```
 
+### Debug logging
+
 With debug logging enabled:
 
 ```sh
@@ -145,6 +163,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Client streaming
+
 Client streaming with metadata:
 
 ```sh
@@ -156,6 +176,8 @@ ghz --insecure \
   0.0.0.0:50051
 ```
 
+### Server streaming
+
 Server streaming with metadata:
 
 ```sh
@@ -165,4 +187,34 @@ ghz --insecure \
   -d '{"lo":{"latitude":400000000,"longitude":-750000000},"hi":{"latitude":420000000,"longitude":-730000000}}' \
   -m '{"trace_id":"{{.RequestNumber}}", "timestamp":"{{.TimestampUnixNano}}"}' \
   0.0.0.0:50051
-```
\ No newline at end of file
+```
+
+### Well Known Types
+
+[Well known types](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf) can be used:
+
+Example proto:
+
+```proto
+syntax = "proto3";
+
+package wrapped;
+
+option go_package = "internal/wrapped";
+
+import "google/protobuf/wrappers.proto";
+
+service WrappedService {
+  rpc GetMessage (google.protobuf.StringValue) returns (google.protobuf.StringValue);
+}
+```
+
+We can test the call:
+
+```sh
+ghz --insecure \
+  --proto ./testdata/wrapped.proto \
+  --call wrapped.WrappedService.GetMessage \
+  -d '"asdf"' \
+  0.0.0.0:50051
+```


Test patch:
diff --git a/runner/data_test.go b/runner/data_test.go
index 796e96e..33592e2 100644
--- a/runner/data_test.go
+++ b/runner/data_test.go
@@ -2,6 +2,7 @@ package runner
 
 import (
 	"encoding/json"
+	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -45,6 +46,14 @@ func TestData_createPayloads(t *testing.T) {
 	assert.NoError(t, err)
 	assert.NotNil(t, mtdTestUnaryTwo)
 
+	mtdWrapped, err := protodesc.GetMethodDescFromProto(
+		"wrapped.WrappedService.GetMessage",
+		"../testdata/wrapped.proto",
+		[]string{"../testdata"})
+
+	assert.NoError(t, err)
+	assert.NotNil(t, mtdUnary)
+
 	t.Run("get empty when empty", func(t *testing.T) {
 		inputs, err := createPayloadsFromJSON("", mtdUnary)
 		assert.NoError(t, err)
@@ -204,6 +213,18 @@ func TestData_createPayloads(t *testing.T) {
 		assert.NotNil(t, inputs[0])
 	})
 
+	t.Run("create data with well-known wrapped type", func(t *testing.T) {
+		jsonData := `"foo"`
+
+		inputs, err := createPayloadsFromJSON(jsonData, mtdWrapped)
+		assert.NoError(t, err)
+		fmt.Println(err)
+		fmt.Println(inputs)
+		assert.NotNil(t, inputs)
+		assert.Len(t, inputs, 1)
+		assert.EqualValues(t, "foo", inputs[0].GetFieldByName("value"))
+	})
+
 	t.Run("create slice from single message binary data", func(t *testing.T) {
 		msg1 := &helloworld.HelloRequest{}
 		msg1.Name = "bob"
diff --git a/runner/run_test.go b/runner/run_test.go
index 88fb7d1..4c7aaf7 100644
--- a/runner/run_test.go
+++ b/runner/run_test.go
@@ -1059,3 +1059,84 @@ func TestRunUnaryDurationStop(t *testing.T) {
 		assert.Equal(t, 6, report.StatusCodeDist["OK"])
 	})
 }
+
+func TestRunWrappedUnary(t *testing.T) {
+
+	_, s, err := internal.StartWrappedServer(false)
+
+	if err != nil {
+		assert.FailNow(t, err.Error())
+	}
+
+	defer s.Stop()
+
+	t.Run("json string data", func(t *testing.T) {
+		report, err := Run(
+			"wrapped.WrappedService.GetMessage",
+			internal.TestLocalhost,
+			WithProtoFile("../testdata/wrapped.proto", []string{"../testdata"}),
+			WithTotalRequests(1),
+			WithConcurrency(1),
+			WithTimeout(time.Duration(20*time.Second)),
+			WithDialTimeout(time.Duration(20*time.Second)),
+			WithDataFromJSON(`"foo"`),
+			WithInsecure(true),
+		)
+
+		assert.NoError(t, err)
+
+		assert.NotNil(t, report)
+
+		assert.Equal(t, 1, int(report.Count))
+		assert.NotZero(t, report.Average)
+		assert.NotZero(t, report.Fastest)
+		assert.NotZero(t, report.Slowest)
+		assert.NotZero(t, report.Rps)
+		assert.Empty(t, report.Name)
+		assert.NotEmpty(t, report.Date)
+		assert.NotEmpty(t, report.Options)
+		assert.NotEmpty(t, report.Details)
+		assert.Equal(t, true, report.Options.Insecure)
+		assert.NotEmpty(t, report.LatencyDistribution)
+		assert.Equal(t, ReasonNormalEnd, report.EndReason)
+		assert.Empty(t, report.ErrorDist)
+
+		assert.Equal(t, report.Average, report.Slowest)
+		assert.Equal(t, report.Average, report.Fastest)
+	})
+
+	t.Run("json string data from file", func(t *testing.T) {
+		report, err := Run(
+			"wrapped.WrappedService.GetMessage",
+			internal.TestLocalhost,
+			WithProtoFile("../testdata/wrapped.proto", []string{"../testdata"}),
+			WithTotalRequests(1),
+			WithConcurrency(1),
+			WithTimeout(time.Duration(20*time.Second)),
+			WithDialTimeout(time.Duration(20*time.Second)),
+			WithDataFromFile(`../testdata/wrapped_data.json`),
+			WithInsecure(true),
+		)
+
+		assert.NoError(t, err)
+
+		assert.NotNil(t, report)
+
+		assert.Equal(t, 1, int(report.Count))
+		assert.NotZero(t, report.Average)
+		assert.NotZero(t, report.Fastest)
+		assert.NotZero(t, report.Slowest)
+		assert.NotZero(t, report.Rps)
+		assert.Empty(t, report.Name)
+		assert.NotEmpty(t, report.Date)
+		assert.NotEmpty(t, report.Options)
+		assert.NotEmpty(t, report.Details)
+		assert.Equal(t, true, report.Options.Insecure)
+		assert.NotEmpty(t, report.LatencyDistribution)
+		assert.Equal(t, ReasonNormalEnd, report.EndReason)
+		assert.Empty(t, report.ErrorDist)
+
+		assert.Equal(t, report.Average, report.Slowest)
+		assert.Equal(t, report.Average, report.Fastest)
+	})
+}
diff --git a/testdata/google/protobuf/wrappers.proto b/testdata/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..b7dd90e
--- /dev/null
+++ b/testdata/google/protobuf/wrappers.proto
@@ -0,0 +1,123 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+//
+// These wrappers have no meaningful use within repeated fields as they lack
+// the ability to detect presence on individual elements.
+// These wrappers have no meaningful use within a map or a oneof since
+// individual entries of a map or fields of a oneof can already detect presence.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/wrappers";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+  // The double value.
+  double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+  // The float value.
+  float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+  // The int64 value.
+  int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+  // The uint64 value.
+  uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+  // The int32 value.
+  int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+  // The uint32 value.
+  uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+  // The bool value.
+  bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+  // The string value.
+  string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+  // The bytes value.
+  bytes value = 1;
+}
\ No newline at end of file
diff --git a/testdata/wrapped.proto b/testdata/wrapped.proto
new file mode 100644
index 0000000..42ffbbb
--- /dev/null
+++ b/testdata/wrapped.proto
@@ -0,0 +1,11 @@
+syntax = "proto3";
+
+package wrapped;
+
+option go_package = "internal/wrapped";
+
+import "google/protobuf/wrappers.proto";
+
+service WrappedService {
+  rpc GetMessage (google.protobuf.StringValue) returns (google.protobuf.StringValue);
+}
\ No newline at end of file
diff --git a/testdata/wrapped_data.json b/testdata/wrapped_data.json
new file mode 100644
index 0000000..7e9668e
--- /dev/null
+++ b/testdata/wrapped_data.json
@@ -0,0 +1,1 @@
+"foo"
\ No newline at end of file