| // Copyright 2025 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "slices" |
| "strings" |
| "testing" |
| |
| visibilitypb "google.golang.org/protobuf/cmd/protoc-gen-go/testdata/visibility" |
| "google.golang.org/protobuf/reflect/protodesc" |
| "google.golang.org/protobuf/types/descriptorpb" |
| ) |
| |
| func TestVisibility(t *testing.T) { |
| // Verify that the visibility modifiers aren't lost when converting to descriptor protos. |
| fd := visibilitypb.File_cmd_protoc_gen_go_testdata_visibility_visibility_proto |
| fdProto := protodesc.ToFileDescriptorProto(fd) |
| |
| type testCase struct { |
| name string |
| visibility descriptorpb.SymbolVisibility |
| } |
| testCases := []testCase{ |
| { |
| name: "DefaultMessage", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_UNSET, |
| }, |
| { |
| name: "ExportMessage", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_EXPORT, |
| }, |
| { |
| name: "LocalMessage", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_LOCAL, |
| }, |
| { |
| name: "DefaultEnum", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_UNSET, |
| }, |
| { |
| name: "ExportEnum", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_EXPORT, |
| }, |
| { |
| name: "LocalEnum", |
| visibility: descriptorpb.SymbolVisibility_VISIBILITY_LOCAL, |
| }, |
| } |
| // Instead of the boilerplate of handwritten cases for the nested types, we generate them |
| // algorithmically: |
| baseTestCases := slices.Clone(testCases) |
| for _, topLevelCase := range baseTestCases { |
| if strings.HasSuffix(topLevelCase.name, "Enum") { |
| continue // only messages have nested types |
| } |
| for _, nestedCase := range baseTestCases { |
| nestedCase.name = topLevelCase.name + ".Nested" + nestedCase.name |
| testCases = append(testCases, nestedCase) |
| } |
| } |
| |
| for _, tc := range testCases { |
| t.Run(tc.name, func(t *testing.T) { |
| path := strings.Split(tc.name, ".") |
| decl, visPtr := findByName(fdProto.MessageType, fdProto.EnumType, path) |
| |
| if decl.GetVisibility() != tc.visibility { |
| t.Errorf("expected %v to have %v visibility but instead got %v", decl.GetName(), tc.visibility, decl.GetVisibility()) |
| } |
| if tc.visibility == descriptorpb.SymbolVisibility_VISIBILITY_UNSET { |
| // For this one, we go one step further and expect the raw field to be a nil pointer. |
| if visPtr != nil { |
| t.Errorf("expected %v to have no visibility but instead got %v", decl.GetName(), visPtr) |
| } |
| } |
| }) |
| } |
| } |
| |
| func findByName(msgs []*descriptorpb.DescriptorProto, enums []*descriptorpb.EnumDescriptorProto, path []string) (hasVisibility, *descriptorpb.SymbolVisibility) { |
| name := path[0] |
| if len(path) > 1 { |
| // Looking for nested type. |
| for _, msg := range msgs { |
| if msg.GetName() == name { |
| return findByName(msg.NestedType, msg.EnumType, path[1:]) |
| } |
| } |
| return nil, nil |
| } |
| |
| if strings.HasSuffix(name, "Enum") { |
| for _, enum := range enums { |
| if enum.GetName() == name { |
| return enum, enum.Visibility |
| } |
| } |
| return nil, nil |
| } |
| |
| for _, msg := range msgs { |
| if msg.GetName() == name { |
| return msg, msg.Visibility |
| } |
| } |
| return nil, nil |
| } |
| |
| type hasVisibility interface { |
| GetName() string |
| GetVisibility() descriptorpb.SymbolVisibility |
| } |