diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 1a9538a7df4..429ed00020a 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -295,8 +295,16 @@ func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]M } } messages := fd.Messages() + + stack := make([]protoreflect.MessageDescriptor, 0, messages.Len()) for i := 0; i < messages.Len(); i++ { - message := messages.Get(i) + stack = append(stack, messages.Get(i)) + } + + for len(stack) > 0 { + message := stack[len(stack)-1] + stack = stack[:len(stack)-1] + _, errFind := protoregistry.GlobalTypes.FindMessageByName(message.FullName()) if errors.Is(errFind, protoregistry.NotFound) { err = protoregistry.GlobalTypes.RegisterMessage(dynamicpb.NewMessageType(message)) @@ -304,7 +312,13 @@ func (c *Client) convertToMethodInfo(fdset *descriptorpb.FileDescriptorSet) ([]M return false } } + + nested := message.Messages() + for i := 0; i < nested.Len(); i++ { + stack = append(stack, nested.Get(i)) + } } + return true }) if err != nil { diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index e19e65d6527..57ccce6b148 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -13,6 +13,8 @@ import ( "testing" "google.golang.org/grpc/reflection" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" "github.com/dop251/goja" "github.com/golang/protobuf/ptypes/any" @@ -977,3 +979,64 @@ func TestClientInvokeHeadersDeprecated(t *testing.T) { require.Len(t, entries, 1) require.Contains(t, entries[0].Message, "headers property is deprecated") } + +func TestClientLoadProto(t *testing.T) { + t.Parallel() + + type testState struct { + *modulestest.Runtime + httpBin *httpmultibin.HTTPMultiBin + samples chan metrics.SampleContainer + } + setup := func(t *testing.T) testState { + t.Helper() + + tb := httpmultibin.NewHTTPMultiBin(t) + samples := make(chan metrics.SampleContainer, 1000) + testRuntime := modulestest.NewRuntime(t) + + cwd, err := os.Getwd() //nolint:forbidigo + require.NoError(t, err) + fs := fsext.NewOsFs() + if isWindows { + fs = fsext.NewTrimFilePathSeparatorFs(fs) + } + testRuntime.VU.InitEnvField.CWD = &url.URL{Path: cwd} + testRuntime.VU.InitEnvField.FileSystems = map[string]fsext.Fs{"file": fs} + + return testState{ + Runtime: testRuntime, + httpBin: tb, + samples: samples, + } + } + + ts := setup(t) + + m, ok := New().NewModuleInstance(ts.VU).(*ModuleInstance) + require.True(t, ok) + require.NoError(t, ts.VU.Runtime().Set("grpc", m.Exports().Named)) + + code := ` + var client = new grpc.Client(); + client.load([], "../../../../lib/testutils/httpmultibin/grpc_testing/nested_types.proto");` + + _, err := ts.VU.Runtime().RunString(ts.httpBin.Replacer.Replace(code)) + assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) + + expectedTypes := []string{ + "grpc.testing.Outer", + "grpc.testing.Outer.MiddleAA", + "grpc.testing.Outer.MiddleAA.Inner", + "grpc.testing.Outer.MiddleBB", + "grpc.testing.Outer.MiddleBB.Inner", + "grpc.testing.MeldOuter", + } + + for _, expected := range expectedTypes { + found, err := protoregistry.GlobalTypes.FindMessageByName(protoreflect.FullName(expected)) + + assert.NotNil(t, found, "Expected to find the message type %s, but an error occurred", expected) + assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) + } +} diff --git a/lib/testutils/httpmultibin/grpc_testing/nested_types.proto b/lib/testutils/httpmultibin/grpc_testing/nested_types.proto new file mode 100644 index 00000000000..26eaac904d6 --- /dev/null +++ b/lib/testutils/httpmultibin/grpc_testing/nested_types.proto @@ -0,0 +1,36 @@ +// The purpose of this proto file is to demonstrate that we can have +// nested types and that we should be able to load them correctly. + +syntax = "proto3"; + +package grpc.testing; + +// Example to demonstrate that it is possible to define +// and use message types within other message types +message Outer { // Level 0 + message MiddleAA { // Level 1 + message Inner { // Level 2 + int64 ival = 1; + bool booly = 2; + } + Inner inner = 1; + } + + message MiddleBB { // Level 1 + message Inner { // Level 2 + int32 ival = 1; + bool booly = 2; + } + Inner inner = 1; + } + + MiddleAA middleAA = 1; + MiddleBB middleBB = 2; +} + +// Example to demonstrate that it is possible to reuse +// a message type outside its parent message type +message MeldOuter { + Outer.MiddleAA.Inner innerAA = 1; + Outer.MiddleBB.Inner innerBB = 2; +}