...

Source file src/google.golang.org/protobuf/compiler/protogen/protogen_test.go

Documentation: google.golang.org/protobuf/compiler/protogen

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package protogen
     6  
     7  import (
     8  	"flag"
     9  	"fmt"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  
    14  	"google.golang.org/protobuf/internal/genid"
    15  	"google.golang.org/protobuf/proto"
    16  	"google.golang.org/protobuf/reflect/protoreflect"
    17  	"google.golang.org/protobuf/testing/protocmp"
    18  
    19  	"google.golang.org/protobuf/types/descriptorpb"
    20  	"google.golang.org/protobuf/types/pluginpb"
    21  )
    22  
    23  func TestPluginParameters(t *testing.T) {
    24  	var flags flag.FlagSet
    25  	value := flags.Int("integer", 0, "")
    26  	const params = "integer=2"
    27  	_, err := Options{
    28  		ParamFunc: flags.Set,
    29  	}.New(&pluginpb.CodeGeneratorRequest{
    30  		Parameter: proto.String(params),
    31  	})
    32  	if err != nil {
    33  		t.Errorf("New(generator parameters %q): %v", params, err)
    34  	}
    35  	if *value != 2 {
    36  		t.Errorf("New(generator parameters %q): integer=%v, want 2", params, *value)
    37  	}
    38  }
    39  
    40  func TestPluginParameterErrors(t *testing.T) {
    41  	for _, parameter := range []string{
    42  		"unknown=1",
    43  		"boolean=error",
    44  	} {
    45  		var flags flag.FlagSet
    46  		flags.Bool("boolean", false, "")
    47  		_, err := Options{
    48  			ParamFunc: flags.Set,
    49  		}.New(&pluginpb.CodeGeneratorRequest{
    50  			Parameter: proto.String(parameter),
    51  		})
    52  		if err == nil {
    53  			t.Errorf("New(generator parameters %q): want error, got nil", parameter)
    54  		}
    55  	}
    56  }
    57  
    58  func TestNoGoPackage(t *testing.T) {
    59  	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
    60  		ProtoFile: []*descriptorpb.FileDescriptorProto{
    61  			{
    62  				Name:    proto.String("testdata/go_package/no_go_package.proto"),
    63  				Syntax:  proto.String(protoreflect.Proto3.String()),
    64  				Package: proto.String("goproto.testdata"),
    65  			},
    66  		},
    67  	})
    68  	if err == nil {
    69  		t.Fatalf("missing go_package option: New(req) = nil, want error")
    70  	}
    71  }
    72  
    73  func TestInvalidImportPath(t *testing.T) {
    74  	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
    75  		ProtoFile: []*descriptorpb.FileDescriptorProto{
    76  			{
    77  				Name:    proto.String("testdata/go_package/no_go_package.proto"),
    78  				Syntax:  proto.String(protoreflect.Proto3.String()),
    79  				Package: proto.String("goproto.testdata"),
    80  				Options: &descriptorpb.FileOptions{
    81  					GoPackage: proto.String("foo"),
    82  				},
    83  			},
    84  		},
    85  	})
    86  	if err == nil {
    87  		t.Fatalf("missing go_package option: New(req) = nil, want error")
    88  	}
    89  }
    90  
    91  func TestPackageNamesAndPaths(t *testing.T) {
    92  	const (
    93  		filename         = "dir/filename.proto"
    94  		protoPackageName = "proto.package"
    95  	)
    96  	for _, test := range []struct {
    97  		desc            string
    98  		parameter       string
    99  		goPackageOption string
   100  		generate        bool
   101  		wantPackageName GoPackageName
   102  		wantImportPath  GoImportPath
   103  		wantFilename    string
   104  	}{
   105  		{
   106  			desc:            "go_package option sets import path",
   107  			goPackageOption: "golang.org/x/foo",
   108  			generate:        true,
   109  			wantPackageName: "foo",
   110  			wantImportPath:  "golang.org/x/foo",
   111  			wantFilename:    "golang.org/x/foo/filename",
   112  		},
   113  		{
   114  			desc:            "go_package option sets import path without slashes",
   115  			goPackageOption: "golang.org;foo",
   116  			generate:        true,
   117  			wantPackageName: "foo",
   118  			wantImportPath:  "golang.org",
   119  			wantFilename:    "golang.org/filename",
   120  		},
   121  		{
   122  			desc:            "go_package option sets import path and package",
   123  			goPackageOption: "golang.org/x/foo;bar",
   124  			generate:        true,
   125  			wantPackageName: "bar",
   126  			wantImportPath:  "golang.org/x/foo",
   127  			wantFilename:    "golang.org/x/foo/filename",
   128  		},
   129  		{
   130  			desc:            "command line sets import path for a file",
   131  			parameter:       "Mdir/filename.proto=golang.org/x/bar",
   132  			goPackageOption: "golang.org/x/foo",
   133  			generate:        true,
   134  			wantPackageName: "foo",
   135  			wantImportPath:  "golang.org/x/bar",
   136  			wantFilename:    "golang.org/x/bar/filename",
   137  		},
   138  		{
   139  			desc:            "command line sets import path for a file with package name specified",
   140  			parameter:       "Mdir/filename.proto=golang.org/x/bar;bar",
   141  			goPackageOption: "golang.org/x/foo",
   142  			generate:        true,
   143  			wantPackageName: "bar",
   144  			wantImportPath:  "golang.org/x/bar",
   145  			wantFilename:    "golang.org/x/bar/filename",
   146  		},
   147  		{
   148  			desc:            "module option set",
   149  			parameter:       "module=golang.org/x",
   150  			goPackageOption: "golang.org/x/foo",
   151  			generate:        false,
   152  			wantPackageName: "foo",
   153  			wantImportPath:  "golang.org/x/foo",
   154  			wantFilename:    "foo/filename",
   155  		},
   156  		{
   157  			desc:            "paths=import uses import path from command line",
   158  			parameter:       "paths=import,Mdir/filename.proto=golang.org/x/bar",
   159  			goPackageOption: "golang.org/x/foo",
   160  			generate:        true,
   161  			wantPackageName: "foo",
   162  			wantImportPath:  "golang.org/x/bar",
   163  			wantFilename:    "golang.org/x/bar/filename",
   164  		},
   165  		{
   166  			desc:            "module option implies paths=import",
   167  			parameter:       "module=golang.org/x,Mdir/filename.proto=golang.org/x/foo",
   168  			generate:        false,
   169  			wantPackageName: "foo",
   170  			wantImportPath:  "golang.org/x/foo",
   171  			wantFilename:    "foo/filename",
   172  		},
   173  	} {
   174  		context := fmt.Sprintf(`
   175  TEST: %v
   176    --go_out=%v:.
   177    file %q: generate=%v
   178    option go_package = %q;
   179  
   180    `,
   181  			test.desc, test.parameter, filename, test.generate, test.goPackageOption)
   182  
   183  		req := &pluginpb.CodeGeneratorRequest{
   184  			Parameter: proto.String(test.parameter),
   185  			ProtoFile: []*descriptorpb.FileDescriptorProto{
   186  				{
   187  					Name:    proto.String(filename),
   188  					Package: proto.String(protoPackageName),
   189  					Options: &descriptorpb.FileOptions{
   190  						GoPackage: proto.String(test.goPackageOption),
   191  					},
   192  				},
   193  			},
   194  		}
   195  		if test.generate {
   196  			req.FileToGenerate = []string{filename}
   197  		}
   198  		gen, err := Options{}.New(req)
   199  		if err != nil {
   200  			t.Errorf("%vNew(req) = %v", context, err)
   201  			continue
   202  		}
   203  		gotFile, ok := gen.FilesByPath[filename]
   204  		if !ok {
   205  			t.Errorf("%v%v: missing file info", context, filename)
   206  			continue
   207  		}
   208  		if got, want := gotFile.GoPackageName, test.wantPackageName; got != want {
   209  			t.Errorf("%vGoPackageName=%v, want %v", context, got, want)
   210  		}
   211  		if got, want := gotFile.GoImportPath, test.wantImportPath; got != want {
   212  			t.Errorf("%vGoImportPath=%v, want %v", context, got, want)
   213  		}
   214  		gen.NewGeneratedFile(gotFile.GeneratedFilenamePrefix, "")
   215  		resp := gen.Response()
   216  		if got, want := resp.File[0].GetName(), test.wantFilename; got != want {
   217  			t.Errorf("%vgenerated filename=%v, want %v", context, got, want)
   218  		}
   219  	}
   220  }
   221  
   222  func TestPackageNameInference(t *testing.T) {
   223  	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
   224  		Parameter: proto.String("Mdir/file1.proto=path/to/file1"),
   225  		ProtoFile: []*descriptorpb.FileDescriptorProto{
   226  			{
   227  				Name:    proto.String("dir/file1.proto"),
   228  				Package: proto.String("proto.package"),
   229  			},
   230  			{
   231  				Name:    proto.String("dir/file2.proto"),
   232  				Package: proto.String("proto.package"),
   233  				Options: &descriptorpb.FileOptions{
   234  					GoPackage: proto.String("path/to/file2"),
   235  				},
   236  			},
   237  		},
   238  		FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
   239  	})
   240  	if err != nil {
   241  		t.Fatalf("New(req) = %v", err)
   242  	}
   243  	if f1, ok := gen.FilesByPath["dir/file1.proto"]; !ok {
   244  		t.Errorf("missing file info for dir/file1.proto")
   245  	} else if f1.GoPackageName != "file1" {
   246  		t.Errorf("dir/file1.proto: GoPackageName=%v, want foo; package name should be derived from dir/file2.proto", f1.GoPackageName)
   247  	}
   248  }
   249  
   250  func TestInconsistentPackageNames(t *testing.T) {
   251  	_, err := Options{}.New(&pluginpb.CodeGeneratorRequest{
   252  		ProtoFile: []*descriptorpb.FileDescriptorProto{
   253  			{
   254  				Name:    proto.String("dir/file1.proto"),
   255  				Package: proto.String("proto.package"),
   256  				Options: &descriptorpb.FileOptions{
   257  					GoPackage: proto.String("golang.org/x/foo"),
   258  				},
   259  			},
   260  			{
   261  				Name:    proto.String("dir/file2.proto"),
   262  				Package: proto.String("proto.package"),
   263  				Options: &descriptorpb.FileOptions{
   264  					GoPackage: proto.String("golang.org/x/foo;bar"),
   265  				},
   266  			},
   267  		},
   268  		FileToGenerate: []string{"dir/file1.proto", "dir/file2.proto"},
   269  	})
   270  	if err == nil {
   271  		t.Fatalf("inconsistent package names for the same import path: New(req) = nil, want error")
   272  	}
   273  }
   274  
   275  func TestImports(t *testing.T) {
   276  	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
   277  	if err != nil {
   278  		t.Fatal(err)
   279  	}
   280  	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
   281  	g.P("package foo")
   282  	g.P()
   283  	for _, importPath := range []GoImportPath{
   284  		"golang.org/x/foo",
   285  		// Multiple references to the same package.
   286  		"golang.org/x/bar",
   287  		"golang.org/x/bar",
   288  		// Reference to a different package with the same basename.
   289  		"golang.org/y/bar",
   290  		"golang.org/x/baz",
   291  		// Reference to a package conflicting with a predeclared identifier.
   292  		"golang.org/z/string",
   293  	} {
   294  		g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: importPath}, " // ", importPath)
   295  	}
   296  	want := `package foo
   297  
   298  import (
   299  	bar "golang.org/x/bar"
   300  	baz "golang.org/x/baz"
   301  	bar1 "golang.org/y/bar"
   302  	string1 "golang.org/z/string"
   303  )
   304  
   305  var _ = X         // "golang.org/x/foo"
   306  var _ = bar.X     // "golang.org/x/bar"
   307  var _ = bar.X     // "golang.org/x/bar"
   308  var _ = bar1.X    // "golang.org/y/bar"
   309  var _ = baz.X     // "golang.org/x/baz"
   310  var _ = string1.X // "golang.org/z/string"
   311  `
   312  	got, err := g.Content()
   313  	if err != nil {
   314  		t.Fatalf("g.Content() = %v", err)
   315  	}
   316  	if diff := cmp.Diff(string(want), string(got)); diff != "" {
   317  		t.Fatalf("content mismatch (-want +got):\n%s", diff)
   318  	}
   319  }
   320  
   321  func TestImportRewrites(t *testing.T) {
   322  	gen, err := Options{
   323  		ImportRewriteFunc: func(i GoImportPath) GoImportPath {
   324  			return "prefix/" + i
   325  		},
   326  	}.New(&pluginpb.CodeGeneratorRequest{})
   327  	if err != nil {
   328  		t.Fatal(err)
   329  	}
   330  	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
   331  	g.P("package foo")
   332  	g.P("var _ = ", GoIdent{GoName: "X", GoImportPath: "golang.org/x/bar"})
   333  	want := `package foo
   334  
   335  import (
   336  	bar "prefix/golang.org/x/bar"
   337  )
   338  
   339  var _ = bar.X
   340  `
   341  	got, err := g.Content()
   342  	if err != nil {
   343  		t.Fatalf("g.Content() = %v", err)
   344  	}
   345  	if diff := cmp.Diff(string(want), string(got)); diff != "" {
   346  		t.Fatalf("content mismatch (-want +got):\n%s", diff)
   347  	}
   348  }
   349  
   350  func TestAnnotations(t *testing.T) {
   351  	gen, err := Options{}.New(&pluginpb.CodeGeneratorRequest{})
   352  	if err != nil {
   353  		t.Fatal(err)
   354  	}
   355  	loc := Location{SourceFile: "foo.go"}
   356  	g := gen.NewGeneratedFile("foo.go", "golang.org/x/foo")
   357  
   358  	g.P("package foo")
   359  	g.P()
   360  
   361  	structName := "S"
   362  	fieldName := "Field"
   363  
   364  	messageLoc := loc.appendPath(genid.FileDescriptorProto_MessageType_field_number, 1)
   365  	fieldLoc := messageLoc.appendPath(genid.DescriptorProto_Field_field_number, 1)
   366  
   367  	g.Annotate(structName, messageLoc) // use deprecated version to test existing usages
   368  	g.P("type ", structName, " struct {")
   369  	g.AnnotateSymbol(structName+"."+fieldName, Annotation{Location: fieldLoc})
   370  	g.P(fieldName, " string")
   371  	g.P("}")
   372  	g.P()
   373  
   374  	g.AnnotateSymbol(fmt.Sprintf("%s.Set%s", structName, fieldName), Annotation{
   375  		Location: fieldLoc,
   376  		Semantic: descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
   377  	})
   378  	g.P("func (m *", structName, ") Set", fieldName, "(x string) {")
   379  	g.P("m.", fieldName, " = x")
   380  	g.P("}")
   381  	g.P()
   382  
   383  	want := &descriptorpb.GeneratedCodeInfo{
   384  		Annotation: []*descriptorpb.GeneratedCodeInfo_Annotation{
   385  			{ // S
   386  				SourceFile: proto.String("foo.go"),
   387  				Path:       []int32{4, 1}, // message S
   388  				Begin:      proto.Int32(18),
   389  				End:        proto.Int32(19),
   390  			},
   391  			{ // S.F
   392  				SourceFile: proto.String("foo.go"),
   393  				Path:       []int32{4, 1, 2, 1},
   394  				Begin:      proto.Int32(30),
   395  				End:        proto.Int32(35),
   396  			},
   397  			{ // SetF
   398  				SourceFile: proto.String("foo.go"),
   399  				Path:       []int32{4, 1, 2, 1},
   400  				Begin:      proto.Int32(58),
   401  				End:        proto.Int32(66),
   402  				Semantic:   descriptorpb.GeneratedCodeInfo_Annotation_SET.Enum(),
   403  			},
   404  		},
   405  	}
   406  
   407  	content, err := g.Content()
   408  	if err != nil {
   409  		t.Fatalf("g.Content() = %v", err)
   410  	}
   411  	got, err := g.generatedCodeInfo(content)
   412  	if err != nil {
   413  		t.Fatalf("g.generatedCodeInfo(...) = %v", err)
   414  	}
   415  	if diff := cmp.Diff(want, got, protocmp.Transform()); diff != "" {
   416  		t.Fatalf("GeneratedCodeInfo mismatch (-want +got):\n%s", diff)
   417  	}
   418  }
   419  

View as plain text