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 }