go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/proto/google/descutil/printer/printer_test.go (about)

     1  // Copyright 2021 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package printer
    16  
    17  import (
    18  	"bytes"
    19  	"os"
    20  	"strings"
    21  	"testing"
    22  
    23  	"google.golang.org/protobuf/proto"
    24  	"google.golang.org/protobuf/types/descriptorpb"
    25  	"google.golang.org/protobuf/types/known/structpb"
    26  
    27  	"go.chromium.org/luci/common/proto/google/descutil"
    28  
    29  	// Register proto extensions defined in util.proto.
    30  	_ "go.chromium.org/luci/common/proto/google/descutil/internal"
    31  
    32  	. "github.com/smartystreets/goconvey/convey"
    33  )
    34  
    35  func TestPrinter(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	Convey("Printer", t, func() {
    39  		protoFile, err := os.ReadFile("../internal/util.proto")
    40  		So(err, ShouldBeNil)
    41  		protoFileLines := strings.Split(string(protoFile), "\n")
    42  
    43  		descFileBytes, err := os.ReadFile("../internal/util.desc")
    44  		So(err, ShouldBeNil)
    45  
    46  		var desc descriptorpb.FileDescriptorSet
    47  		err = proto.Unmarshal(descFileBytes, &desc)
    48  		So(err, ShouldBeNil)
    49  
    50  		var file *descriptorpb.FileDescriptorProto
    51  		for _, filePb := range desc.File {
    52  			if filePb.GetName() == "go.chromium.org/luci/common/proto/google/descutil/internal/util.proto" {
    53  				file = filePb
    54  				break
    55  			}
    56  		}
    57  		// we must find the util_test.proto file in `desc`
    58  		So(file, ShouldNotBeNil)
    59  
    60  		sourceCodeInfo, err := descutil.IndexSourceCodeInfo(file)
    61  		So(err, ShouldBeNil)
    62  
    63  		getExpectedDef := func(ptr any, unindent int) string {
    64  			loc := sourceCodeInfo[ptr]
    65  			So(loc, ShouldNotBeNil)
    66  			startLine := loc.Span[0]
    67  			endLine := startLine
    68  			if len(loc.Span) > 3 {
    69  				endLine = loc.Span[2]
    70  			}
    71  
    72  			for startLine > 0 && strings.HasPrefix(strings.TrimSpace(protoFileLines[startLine-1]), "//") {
    73  				startLine--
    74  			}
    75  
    76  			expected := make([]string, endLine-startLine+1)
    77  			for i := 0; i < len(expected); i++ {
    78  				expected[i] = protoFileLines[int(startLine)+i][unindent:]
    79  			}
    80  
    81  			return strings.Join(expected, "\n") + "\n"
    82  		}
    83  
    84  		var buf bytes.Buffer
    85  		printer := NewPrinter(&buf)
    86  		So(printer.SetFile(file), ShouldBeNil)
    87  
    88  		checkOutput := func(ptr any, unindent int) {
    89  			So(buf.String(), ShouldEqual, getExpectedDef(ptr, unindent))
    90  		}
    91  
    92  		Convey("package", func() {
    93  			printer.Package(file.GetPackage())
    94  			checkOutput(file.Package, 0)
    95  		})
    96  
    97  		Convey("service", func() {
    98  			for _, s := range file.Service {
    99  				Convey(s.GetName(), func() {
   100  					printer.Service(s, -1)
   101  					checkOutput(s, 0)
   102  				})
   103  			}
   104  		})
   105  
   106  		testEnum := func(e *descriptorpb.EnumDescriptorProto, unindent int) {
   107  			Convey(e.GetName(), func() {
   108  				printer.Enum(e)
   109  				checkOutput(e, unindent)
   110  			})
   111  		}
   112  
   113  		Convey("enum", func() {
   114  			for _, e := range file.EnumType {
   115  				testEnum(e, 0)
   116  			}
   117  		})
   118  
   119  		Convey("message", func() {
   120  			var testMsg func(*descriptorpb.DescriptorProto, int)
   121  			testMsg = func(m *descriptorpb.DescriptorProto, unindent int) {
   122  				Convey(m.GetName(), func() {
   123  					if len(m.NestedType) == 0 && len(m.EnumType) == 0 {
   124  						printer.Message(m)
   125  						checkOutput(m, unindent)
   126  					} else {
   127  						for _, m := range m.NestedType {
   128  							testMsg(m, unindent+1)
   129  						}
   130  						for _, e := range m.EnumType {
   131  							testEnum(e, unindent+1)
   132  						}
   133  					}
   134  				})
   135  			}
   136  			for _, m := range file.MessageType {
   137  				testMsg(m, 0)
   138  			}
   139  		})
   140  
   141  		Convey("synthesized message", func() {
   142  			myFakeMessage := mkMessage(
   143  				"myMessage",
   144  				mkField("f1", 1, descriptorpb.FieldDescriptorProto_TYPE_STRING, nil),
   145  				mkField("st", 2, descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, &structpb.Struct{}),
   146  			)
   147  			printer.AppendLeadingComments(myFakeMessage, []string{"Message comment", "second line."})
   148  			printer.AppendLeadingComments(myFakeMessage.Field[0], []string{"simple string"})
   149  			printer.AppendLeadingComments(myFakeMessage.Field[1], []string{"cool message type"})
   150  
   151  			printer.Message(myFakeMessage)
   152  			So(buf.String(), ShouldEqual, `// Message comment
   153  // second line.
   154  message myMessage {
   155  	// simple string
   156  	string f1 = 1;
   157  	// cool message type
   158  	google.protobuf.Struct st = 2;
   159  }
   160  `)
   161  		})
   162  	})
   163  }
   164  
   165  func mkField(name string, num int32, typ descriptorpb.FieldDescriptorProto_Type, msg proto.Message) *descriptorpb.FieldDescriptorProto {
   166  	ret := &descriptorpb.FieldDescriptorProto{}
   167  	ret.Name = &name
   168  	camelName := camel(name)
   169  	ret.JsonName = &camelName
   170  	ret.Number = &num
   171  	ret.Type = &typ
   172  	if typ == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
   173  		fn := string(msg.ProtoReflect().Descriptor().FullName())
   174  		ret.TypeName = &fn
   175  	}
   176  	return ret
   177  }
   178  
   179  func mkMessage(name string, fields ...*descriptorpb.FieldDescriptorProto) *descriptorpb.DescriptorProto {
   180  	ret := &descriptorpb.DescriptorProto{}
   181  	ret.Name = &name
   182  	ret.Field = fields
   183  	return ret
   184  }