github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/proto/resolve_test.go (about)

     1  /* Copyright 2018 The Bazel Authors. All rights reserved.
     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  
    16  package proto
    17  
    18  import (
    19  	"path/filepath"
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/bazelbuild/bazel-gazelle/label"
    25  	"github.com/bazelbuild/bazel-gazelle/repo"
    26  	"github.com/bazelbuild/bazel-gazelle/resolve"
    27  	"github.com/bazelbuild/bazel-gazelle/rule"
    28  	bzl "github.com/bazelbuild/buildtools/build"
    29  )
    30  
    31  func TestResolveProto(t *testing.T) {
    32  	type buildFile struct {
    33  		rel, content string
    34  	}
    35  	type testCase struct {
    36  		desc      string
    37  		index     []buildFile
    38  		old, want string
    39  	}
    40  	for _, tc := range []testCase{
    41  		{
    42  			desc: "well_known",
    43  			index: []buildFile{{
    44  				rel: "google/protobuf",
    45  				content: `
    46  proto_library(
    47      name = "bad_proto",
    48      srcs = ["any.proto"],
    49  )
    50  `,
    51  			}},
    52  			old: `
    53  proto_library(
    54      name = "dep_proto",
    55      _imports = [
    56          "google/protobuf/any.proto",
    57          "google/protobuf/timestamp.proto",
    58      ],
    59  )
    60  `,
    61  			want: `
    62  proto_library(
    63      name = "dep_proto",
    64      deps = [
    65          "@com_google_protobuf//:any_proto",
    66          "@com_google_protobuf//:timestamp_proto",
    67      ],
    68  )
    69  `,
    70  		}, {
    71  			desc: "override",
    72  			index: []buildFile{
    73  				{
    74  					rel: "google/rpc",
    75  					content: `
    76  proto_library(
    77      name = "bad_proto",
    78      srcs = ["status.proto"],
    79  )
    80  `,
    81  				}, {
    82  					rel: "",
    83  					content: `
    84  # gazelle:resolve proto google/rpc/status.proto //:good_proto
    85  `,
    86  				},
    87  			},
    88  			old: `
    89  proto_library(
    90      name = "dep_proto",
    91      _imports = ["google/rpc/status.proto"],
    92  )
    93  `,
    94  			want: `
    95  proto_library(
    96      name = "dep_proto",
    97      deps = ["//:good_proto"],
    98  )
    99  `,
   100  		}, {
   101  			desc: "index",
   102  			index: []buildFile{{
   103  				rel: "foo",
   104  				content: `
   105  proto_library(
   106      name = "foo_proto",
   107      srcs = ["foo.proto"],
   108  )
   109  `,
   110  			}},
   111  			old: `
   112  proto_library(
   113      name = "dep_proto",
   114      _imports = ["foo/foo.proto"],
   115  )
   116  `,
   117  			want: `
   118  proto_library(
   119      name = "dep_proto",
   120      deps = ["//foo:foo_proto"],
   121  )
   122  `,
   123  		}, {
   124  			desc: "index_local",
   125  			old: `
   126  proto_library(
   127      name = "foo_proto",
   128      srcs = ["foo.proto"],
   129  )
   130  
   131  proto_library(
   132      name = "dep_proto",
   133      _imports = ["test/foo.proto"],
   134  )
   135  `,
   136  			want: `
   137  proto_library(
   138      name = "foo_proto",
   139      srcs = ["foo.proto"],
   140  )
   141  
   142  proto_library(
   143      name = "dep_proto",
   144      deps = [":foo_proto"],
   145  )
   146  `,
   147  		}, {
   148  			desc: "index_ambiguous",
   149  			index: []buildFile{{
   150  				rel: "foo",
   151  				content: `
   152  proto_library(
   153      name = "a_proto",
   154      srcs = ["foo.proto"],
   155  )
   156  
   157  proto_library(
   158      name = "b_proto",
   159      srcs = ["foo.proto"],
   160  )
   161  `,
   162  			}},
   163  			old: `
   164  proto_library(
   165      name = "dep_proto",
   166      _imports = ["foo/foo.proto"],
   167  )
   168  `,
   169  			want: `proto_library(name = "dep_proto")`,
   170  		}, {
   171  			desc: "index_self",
   172  			old: `
   173  proto_library(
   174      name = "dep_proto",
   175      srcs = ["foo.proto"],
   176      _imports = ["test/foo.proto"],
   177  )
   178  `,
   179  			want: `
   180  proto_library(
   181      name = "dep_proto",
   182      srcs = ["foo.proto"],
   183  )
   184  `,
   185  		}, {
   186  			desc: "index_dedup",
   187  			index: []buildFile{{
   188  				rel: "foo",
   189  				content: `
   190  proto_library(
   191      name = "foo_proto",
   192      srcs = [
   193          "a.proto",
   194          "b.proto",
   195      ],
   196  )
   197  `,
   198  			}},
   199  			old: `
   200  proto_library(
   201      name = "dep_proto",
   202      srcs = ["dep.proto"],
   203      _imports = [
   204          "foo/a.proto",
   205          "foo/b.proto",
   206      ],
   207  )
   208  `,
   209  			want: `
   210  proto_library(
   211      name = "dep_proto",
   212      srcs = ["dep.proto"],
   213      deps = ["//foo:foo_proto"],
   214  )
   215  `,
   216  		}, {
   217  			desc: "unknown",
   218  			old: `
   219  proto_library(
   220      name = "dep_proto",
   221      _imports = ["foo/bar/unknown.proto"],
   222  )
   223  `,
   224  			want: `
   225  proto_library(
   226      name = "dep_proto",
   227      deps = ["//foo/bar:bar_proto"],
   228  )
   229  `,
   230  		}, {
   231  			desc: "strip_import_prefix",
   232  			index: []buildFile{{
   233  				rel: "foo/bar/sub",
   234  				content: `
   235  proto_library(
   236      name = "foo_proto",
   237      srcs = ["foo.proto"],
   238      strip_import_prefix = "/foo/bar",
   239  )
   240  `,
   241  			}},
   242  			old: `
   243  proto_library(
   244      name = "dep_proto",
   245      _imports = ["sub/foo.proto"],
   246  )
   247  `,
   248  			want: `
   249  proto_library(
   250      name = "dep_proto",
   251      deps = ["//foo/bar/sub:foo_proto"],
   252  )
   253  `,
   254  		}, {
   255  			desc: "skip bad strip_import_prefix",
   256  			index: []buildFile{{
   257  				rel: "bar",
   258  				content: `
   259  proto_library(
   260      name = "foo_proto",
   261      srcs = ["foo.proto"],
   262      strip_import_prefix = "/foo",
   263  )
   264  `,
   265  			}},
   266  			old: `
   267  proto_library(
   268      name = "dep_proto",
   269      _imports = ["bar/foo.proto"],
   270  )
   271  `,
   272  			want: `
   273  proto_library(
   274      name = "dep_proto",
   275      deps = ["//bar:bar_proto"],
   276  )
   277  `,
   278  		}, {
   279  			desc: "import_prefix",
   280  			index: []buildFile{{
   281  				rel: "bar",
   282  				content: `
   283  proto_library(
   284      name = "foo_proto",
   285      srcs = ["foo.proto"],
   286      import_prefix = "foo/",
   287  )
   288  `,
   289  			}},
   290  			old: `
   291  proto_library(
   292      name = "dep_proto",
   293      _imports = ["foo/bar/foo.proto"],
   294  )
   295  `,
   296  			want: `
   297  proto_library(
   298      name = "dep_proto",
   299      deps = ["//bar:foo_proto"],
   300  )
   301  `,
   302  		}, {
   303  			desc: "strip_import_prefix and import_prefix",
   304  			index: []buildFile{{
   305  				rel: "foo",
   306  				content: `
   307  proto_library(
   308      name = "foo_proto",
   309      srcs = ["foo.proto"],
   310      import_prefix = "bar/",
   311      strip_import_prefix = "/foo",
   312  )
   313  `,
   314  			}},
   315  			old: `
   316  proto_library(
   317      name = "dep_proto",
   318      _imports = ["bar/foo.proto"],
   319  )
   320  `,
   321  			want: `
   322  proto_library(
   323      name = "dep_proto",
   324      deps = ["//foo:foo_proto"],
   325  )
   326  `,
   327  		}, {
   328  			desc: "test single file resolution in file mode",
   329  			index: []buildFile{{
   330  				rel: "somedir",
   331  				content: `
   332  # gazelle:proto file
   333  
   334  proto_library(
   335      name = "foo_proto",
   336      srcs = ["foo.proto"],
   337  )
   338  
   339  proto_library(
   340      name = "bar_proto",
   341      srcs = ["bar.proto"],
   342  )
   343  
   344  proto_library(
   345      name = "baz_proto",
   346      srcs = ["baz.proto"],
   347  )
   348  `,
   349  			}},
   350  			old: `
   351  proto_library(
   352      name = "other_proto",
   353      _imports = ["somedir/bar.proto"],
   354  )
   355  `,
   356  			want: `
   357  proto_library(
   358      name = "other_proto",
   359      deps = ["//somedir:bar_proto"],
   360  )
   361  `,
   362  		}, {
   363  			desc: "test single file resolution in same package",
   364  			old: `
   365  proto_library(
   366      name = "qwerty_proto",
   367      srcs = ["qwerty.proto"],
   368  )
   369  
   370  proto_library(
   371      name = "other_proto",
   372      _imports = ["test/qwerty.proto"],
   373  )
   374  `,
   375  			want: `
   376  proto_library(
   377      name = "qwerty_proto",
   378      srcs = ["qwerty.proto"],
   379  )
   380  
   381  proto_library(
   382      name = "other_proto",
   383      deps = [":qwerty_proto"],
   384  )
   385  `,
   386  		},
   387  	} {
   388  		t.Run(tc.desc, func(t *testing.T) {
   389  			c, lang, cexts := testConfig(t, ".")
   390  			mrslv := make(mapResolver)
   391  			mrslv["proto_library"] = lang
   392  			ix := resolve.NewRuleIndex(mrslv.Resolver, []resolve.CrossResolver{lang.(resolve.CrossResolver)})
   393  			rc := (*repo.RemoteCache)(nil)
   394  			for _, bf := range tc.index {
   395  				f, err := rule.LoadData(filepath.Join(bf.rel, "BUILD.bazel"), bf.rel, []byte(bf.content))
   396  				if err != nil {
   397  					t.Fatal(err)
   398  				}
   399  				if bf.rel == "" {
   400  					for _, cext := range cexts {
   401  						cext.Configure(c, "", f)
   402  					}
   403  				}
   404  				for _, r := range f.Rules {
   405  					ix.AddRule(c, r, f)
   406  				}
   407  			}
   408  			f, err := rule.LoadData("test/BUILD.bazel", "test", []byte(tc.old))
   409  			if err != nil {
   410  				t.Fatal(err)
   411  			}
   412  			imports := make([]interface{}, len(f.Rules))
   413  			for i, r := range f.Rules {
   414  				imports[i] = convertImportsAttr(r)
   415  				ix.AddRule(c, r, f)
   416  			}
   417  			ix.Finish()
   418  			for i, r := range f.Rules {
   419  				lang.Resolve(c, ix, rc, r, imports[i], label.New("", "test", r.Name()))
   420  			}
   421  			f.Sync()
   422  			got := strings.TrimSpace(string(bzl.Format(f.File)))
   423  			want := strings.TrimSpace(tc.want)
   424  			if got != want {
   425  				t.Errorf("got:\n%s\nwant:\n%s", got, want)
   426  			}
   427  		})
   428  	}
   429  }
   430  
   431  func TestCrossResolve(t *testing.T) {
   432  	type testCase struct {
   433  		desc      string
   434  		protoMode Mode
   435  		imp       resolve.ImportSpec
   436  		lang      string
   437  		want      []resolve.FindResult
   438  	}
   439  	for _, tc := range []testCase{
   440  		{
   441  			desc:      "disable global mode go",
   442  			protoMode: DisableGlobalMode,
   443  			imp:       resolve.ImportSpec{Lang: "go", Imp: "github.com/golang/protobuf/proto"},
   444  			lang:      "go",
   445  			want:      nil,
   446  		},
   447  		{
   448  			desc:      "disable global mode proto",
   449  			protoMode: DisableGlobalMode,
   450  			imp:       resolve.ImportSpec{Lang: "proto", Imp: "google/protobuf/any.proto"},
   451  			lang:      "go",
   452  			want:      nil,
   453  		},
   454  		{
   455  			desc:      "proto source lang",
   456  			protoMode: DefaultMode,
   457  			imp:       resolve.ImportSpec{Lang: "proto", Imp: "google/protobuf/any.proto"},
   458  			lang:      "proto",
   459  			want:      nil,
   460  		},
   461  		{
   462  			desc:      "unsupported import lang",
   463  			protoMode: DefaultMode,
   464  			imp:       resolve.ImportSpec{Lang: "foo", Imp: "foo"},
   465  			lang:      "go",
   466  			want:      nil,
   467  		},
   468  		{
   469  			desc:      "go unknown import",
   470  			protoMode: DefaultMode,
   471  			imp:       resolve.ImportSpec{Lang: "go", Imp: "foo"},
   472  			lang:      "go",
   473  			want:      nil,
   474  		},
   475  		{
   476  			desc:      "proto known import",
   477  			protoMode: DefaultMode,
   478  			imp:       resolve.ImportSpec{Lang: "proto", Imp: "google/protobuf/any.proto"},
   479  			lang:      "go",
   480  			want:      []resolve.FindResult{{Label: label.New("com_github_golang_protobuf", "ptypes/any", "any")}},
   481  		},
   482  		{
   483  			desc:      "proto unknown import",
   484  			protoMode: DefaultMode,
   485  			imp:       resolve.ImportSpec{Lang: "proto", Imp: "foo.proto"},
   486  			lang:      "go",
   487  			want:      nil,
   488  		},
   489  	} {
   490  		t.Run(tc.desc, func(t *testing.T) {
   491  			c, lang, _ := testConfig(t, ".")
   492  			pc := GetProtoConfig(c)
   493  			pc.Mode = tc.protoMode
   494  			ix := (*resolve.RuleIndex)(nil)
   495  			got := lang.(resolve.CrossResolver).CrossResolve(c, ix, tc.imp, tc.lang)
   496  			if !reflect.DeepEqual(got, tc.want) {
   497  				t.Errorf("got %#v ; want %#v", got, tc.want)
   498  			}
   499  		})
   500  	}
   501  }
   502  
   503  func convertImportsAttr(r *rule.Rule) interface{} {
   504  	value := r.AttrStrings("_imports")
   505  	if value == nil {
   506  		value = []string(nil)
   507  	}
   508  	r.DelAttr("_imports")
   509  	return value
   510  }
   511  
   512  type mapResolver map[string]resolve.Resolver
   513  
   514  func (mr mapResolver) Resolver(r *rule.Rule, f string) resolve.Resolver {
   515  	return mr[r.Kind()]
   516  }