1
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
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
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
116
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
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
278
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
304
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
357
358
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 }
367
View as plain text