github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/go/generate_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 golang 17 18 import ( 19 "os" 20 "path" 21 "path/filepath" 22 "runtime" 23 "strings" 24 "testing" 25 26 "github.com/bazelbuild/bazel-gazelle/config" 27 "github.com/bazelbuild/bazel-gazelle/language" 28 "github.com/bazelbuild/bazel-gazelle/language/proto" 29 "github.com/bazelbuild/bazel-gazelle/merger" 30 "github.com/bazelbuild/bazel-gazelle/rule" 31 "github.com/bazelbuild/bazel-gazelle/walk" 32 bzl "github.com/bazelbuild/buildtools/build" 33 "github.com/bazelbuild/rules_go/go/tools/bazel" 34 "github.com/google/go-cmp/cmp" 35 ) 36 37 func TestGenerateRules(t *testing.T) { 38 testdataDir := "testdata" 39 if runtime.GOOS == "windows" { 40 var err error 41 testdataDir, err = bazel.NewTmpDir("testdata") 42 if err != nil { 43 t.Fatal(err) 44 } 45 files, _ := bazel.ListRunfiles() 46 parent := "language/go/testdata" 47 for _, rf := range files { 48 rel, err := filepath.Rel(parent, rf.ShortPath) 49 if err != nil { 50 continue 51 } 52 if strings.HasPrefix(rel, "..") { 53 // make sure we're not moving around file that we're not inrerested in 54 continue 55 } 56 newPath := filepath.FromSlash(path.Join(testdataDir, rel)) 57 if err := os.MkdirAll(filepath.FromSlash(filepath.Dir(newPath)), os.ModePerm); err != nil { 58 t.Fatal(err) 59 } 60 if err := os.Link(filepath.FromSlash(rf.Path), newPath); err != nil { 61 t.Fatal(err) 62 } 63 } 64 } 65 66 c, langs, cexts := testConfig( 67 t, 68 "-build_file_name=BUILD.old", 69 "-go_prefix=example.com/repo", 70 "-repo_root="+testdataDir) 71 72 // runfiles are symbolic links, which we need Walk to follow. 73 content := []byte(` 74 # gazelle:follow ** 75 `) 76 f, err := rule.LoadData(filepath.FromSlash("BUILD.config"), "config", content) 77 if err != nil { 78 t.Fatal(err) 79 } 80 for _, cext := range cexts { 81 cext.Configure(c, "", f) 82 } 83 84 var loads []rule.LoadInfo 85 for _, lang := range langs { 86 loads = append(loads, lang.(language.ModuleAwareLanguage).ApparentLoads(func(string) string { return "" })...) 87 } 88 var testsFound int 89 walk.Walk(c, cexts, []string{testdataDir}, walk.VisitAllUpdateSubdirsMode, func(dir, rel string, c *config.Config, update bool, oldFile *rule.File, subdirs, regularFiles, genFiles []string) { 90 t.Run(rel, func(t *testing.T) { 91 var empty, gen []*rule.Rule 92 for _, lang := range langs { 93 res := lang.GenerateRules(language.GenerateArgs{ 94 Config: c, 95 Dir: dir, 96 Rel: rel, 97 File: oldFile, 98 Subdirs: subdirs, 99 RegularFiles: regularFiles, 100 GenFiles: genFiles, 101 OtherEmpty: empty, 102 OtherGen: gen, 103 }) 104 empty = append(empty, res.Empty...) 105 gen = append(gen, res.Gen...) 106 } 107 isTest := false 108 for _, name := range regularFiles { 109 if name == "BUILD.want" { 110 isTest = true 111 break 112 } 113 } 114 if !isTest { 115 // GenerateRules may have side effects, so we need to run it, even if 116 // there's no test. 117 return 118 } 119 testsFound += 1 120 f := rule.EmptyFile("test", "") 121 for _, r := range gen { 122 r.Insert(f) 123 } 124 convertImportsAttrs(f) 125 merger.FixLoads(f, loads) 126 f.Sync() 127 got := string(bzl.Format(f.File)) 128 wantPath := filepath.Join(dir, "BUILD.want") 129 wantBytes, err := os.ReadFile(wantPath) 130 if err != nil { 131 t.Fatalf("error reading %s: %v", wantPath, err) 132 } 133 want := string(wantBytes) 134 want = strings.ReplaceAll(want, "\r\n", "\n") 135 if diff := cmp.Diff(want, got); diff != "" { 136 t.Errorf("(-want, +got): %s", diff) 137 } 138 }) 139 }) 140 // Avoid spurious success if we fail to find any tests. 141 if testsFound == 0 { 142 t.Error("No rule generation tests were found") 143 } 144 } 145 146 func TestGenerateRulesEmpty(t *testing.T) { 147 c, langs, _ := testConfig(t, "-go_prefix=example.com/repo") 148 goLang := langs[1].(*goLang) 149 res := goLang.GenerateRules(language.GenerateArgs{ 150 Config: c, 151 Dir: "./foo", 152 Rel: "foo", 153 }) 154 if len(res.Gen) > 0 { 155 t.Errorf("got %d generated rules; want 0", len(res.Gen)) 156 } 157 f := rule.EmptyFile("test", "") 158 for _, r := range res.Empty { 159 r.Insert(f) 160 } 161 f.Sync() 162 got := strings.TrimSpace(string(bzl.Format(f.File))) 163 want := strings.TrimSpace(` 164 filegroup(name = "go_default_library_protos") 165 166 go_proto_library(name = "foo_go_proto") 167 168 go_library(name = "foo") 169 170 go_binary(name = "foo") 171 172 go_test(name = "foo_test") 173 `) 174 if got != want { 175 t.Errorf("got:\n%s\nwant:\n%s", got, want) 176 } 177 } 178 179 func TestGenerateRulesEmptyLegacyProto(t *testing.T) { 180 c, langs, _ := testConfig(t, "-proto=legacy") 181 goLang := langs[len(langs)-1].(*goLang) 182 res := goLang.GenerateRules(language.GenerateArgs{ 183 Config: c, 184 Dir: "./foo", 185 Rel: "foo", 186 }) 187 for _, e := range res.Empty { 188 if kind := e.Kind(); kind == "proto_library" || kind == "go_proto_library" || kind == "go_grpc_library" { 189 t.Errorf("deleted rule %s ; should not delete in legacy proto mode", kind) 190 } 191 } 192 } 193 194 func TestGenerateRulesEmptyPackageProto(t *testing.T) { 195 c, langs, _ := testConfig(t, "-proto=package", "-go_prefix=example.com/repo") 196 oldContent := []byte(` 197 proto_library( 198 name = "dead_proto", 199 srcs = ["dead.proto"], 200 ) 201 `) 202 old, err := rule.LoadData("BUILD.bazel", "", oldContent) 203 if err != nil { 204 t.Fatal(err) 205 } 206 var empty []*rule.Rule 207 for _, lang := range langs { 208 res := lang.GenerateRules(language.GenerateArgs{ 209 Config: c, 210 Dir: "./foo", 211 Rel: "foo", 212 File: old, 213 OtherEmpty: empty, 214 }) 215 empty = append(empty, res.Empty...) 216 } 217 f := rule.EmptyFile("test", "") 218 for _, r := range empty { 219 r.Insert(f) 220 } 221 f.Sync() 222 got := strings.TrimSpace(string(bzl.Format(f.File))) 223 want := strings.TrimSpace(` 224 proto_library(name = "dead_proto") 225 226 go_proto_library(name = "dead_go_proto") 227 228 filegroup(name = "go_default_library_protos") 229 230 go_proto_library(name = "foo_go_proto") 231 232 go_library(name = "foo") 233 234 go_binary(name = "foo") 235 236 go_test(name = "foo_test") 237 `) 238 if got != want { 239 t.Errorf("got:\n%s\nwant:\n%s", got, want) 240 } 241 } 242 243 func TestGenerateRulesPrebuiltGoProtoRules(t *testing.T) { 244 for _, protoFlag := range []string{ 245 "-proto=default", 246 "-proto=package", 247 } { 248 t.Run("with flag: "+protoFlag, func(t *testing.T) { 249 c, langs, _ := testConfig(t, protoFlag) 250 goLang := langs[len(langs)-1].(*goLang) 251 252 res := goLang.GenerateRules(language.GenerateArgs{ 253 Config: c, 254 Dir: "./foo", 255 Rel: "foo", 256 OtherGen: prebuiltProtoRules(), 257 }) 258 259 if len(res.Gen) != 0 { 260 t.Errorf("got %d generated rules; want 0", len(res.Gen)) 261 } 262 f := rule.EmptyFile("test", "") 263 for _, r := range res.Gen { 264 r.Insert(f) 265 } 266 f.Sync() 267 got := strings.TrimSpace(string(bzl.Format(f.File))) 268 want := strings.TrimSpace(` 269 `) 270 if got != want { 271 t.Errorf("got:\n%s\nwant:\n%s", got, want) 272 } 273 }) 274 } 275 } 276 277 // Test generated files that have been consumed by other rules should not be 278 // added to the go_default_library rule 279 func TestConsumedGenFiles(t *testing.T) { 280 args := language.GenerateArgs{ 281 RegularFiles: []string{"regular.go"}, 282 GenFiles: []string{"mocks.go"}, 283 Config: &config.Config{ 284 Exts: make(map[string]interface{}), 285 }, 286 } 287 otherRule := rule.NewRule("go_library", "go_mock_library") 288 otherRule.SetAttr("srcs", []string{"mocks.go"}) 289 args.OtherGen = append(args.OtherGen, otherRule) 290 291 gl := goLang{ 292 goPkgRels: make(map[string]bool), 293 } 294 gl.Configure(args.Config, "", nil) 295 res := gl.GenerateRules(args) 296 got := res.Gen[0].AttrStrings("srcs") 297 want := []string{"regular.go"} 298 if len(got) != len(want) || got[0] != want[0] { 299 t.Errorf("got:\n%s\nwant:\n%s", got, want) 300 } 301 } 302 303 // Test visibility attribute is only set if no default visibility is provided 304 // by the file or other rules. 305 func TestShouldSetVisibility(t *testing.T) { 306 if !shouldSetVisibility(language.GenerateArgs{}) { 307 t.Error("got 'False' for shouldSetVisibility with default args; expected 'True'") 308 } 309 310 if !shouldSetVisibility(language.GenerateArgs{ 311 File: rule.EmptyFile("path", "pkg"), 312 }) { 313 t.Error("got 'False' for shouldSetVisibility with empty file; expected 'True'") 314 } 315 316 fileWithDefaultVisibile, _ := rule.LoadData("path", "pkg", []byte(`package(default_visibility = "//src:__subpackages__")`)) 317 if shouldSetVisibility(language.GenerateArgs{ 318 File: fileWithDefaultVisibile, 319 }) { 320 t.Error("got 'True' for shouldSetVisibility with file with default visibility; expected 'False'") 321 } 322 323 defaultVisibilityRule := rule.NewRule("package", "") 324 defaultVisibilityRule.SetAttr("default_visibility", []string{"//src:__subpackages__"}) 325 if shouldSetVisibility(language.GenerateArgs{ 326 OtherGen: []*rule.Rule{defaultVisibilityRule}, 327 }) { 328 t.Error("got 'True' for shouldSetVisibility with rule defining a default visibility; expected 'False'") 329 } 330 } 331 332 func prebuiltProtoRules() []*rule.Rule { 333 protoRule := rule.NewRule("proto_library", "foo_proto") 334 protoRule.SetAttr("srcs", []string{"foo.proto"}) 335 protoRule.SetAttr("visibility", []string{"//visibility:public"}) 336 protoRule.SetPrivateAttr(proto.PackageKey, 337 proto.Package{ 338 Name: "foo", 339 Files: map[string]proto.FileInfo{ 340 "foo.proto": {}, 341 }, 342 Imports: map[string]bool{}, 343 Options: map[string]string{}, 344 }, 345 ) 346 347 goProtoRule := rule.NewRule("go_proto_library", "foo_go_proto") 348 goProtoRule.SetAttr("compilers", []string{"@io_bazel_rules_go//proto:go_proto"}) 349 goProtoRule.SetAttr("importpath", "hello/world/foo") 350 goProtoRule.SetAttr("proto", ":foo_proto") 351 protoRule.SetAttr("visibility", []string{"//visibility:public"}) 352 353 return []*rule.Rule{protoRule, goProtoRule} 354 } 355 356 // convertImportsAttrs copies private attributes to regular attributes, which 357 // will later be written out to build files. This allows tests to check the 358 // values of private attributes with simple string comparison. 359 func convertImportsAttrs(f *rule.File) { 360 for _, r := range f.Rules { 361 v := r.PrivateAttr(config.GazelleImportsKey) 362 if v != nil { 363 r.SetAttr(config.GazelleImportsKey, v) 364 } 365 } 366 }