1
15
16 package proto
17
18 import (
19 "os"
20 "path/filepath"
21 "reflect"
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/merger"
29 "github.com/bazelbuild/bazel-gazelle/resolve"
30 "github.com/bazelbuild/bazel-gazelle/rule"
31 "github.com/bazelbuild/bazel-gazelle/testtools"
32 "github.com/bazelbuild/bazel-gazelle/walk"
33
34 bzl "github.com/bazelbuild/buildtools/build"
35 )
36
37 func TestGenerateRules(t *testing.T) {
38 if runtime.GOOS == "windows" {
39
40 if _, err := os.Stat("testdata"); os.IsNotExist(err) {
41 t.Skip("testdata missing on windows due to lack of symbolic links")
42 } else if err != nil {
43 t.Fatal(err)
44 }
45 }
46
47 c, lang, _ := testConfig(t, "testdata")
48
49 walk.Walk(c, []config.Configurer{lang}, []string{"testdata"}, walk.VisitAllUpdateSubdirsMode, func(dir, rel string, c *config.Config, update bool, oldFile *rule.File, subdirs, regularFiles, genFiles []string) {
50 isTest := false
51 for _, name := range regularFiles {
52 if name == "BUILD.want" {
53 isTest = true
54 break
55 }
56 }
57 if !isTest {
58 return
59 }
60 t.Run(rel, func(t *testing.T) {
61 res := lang.GenerateRules(language.GenerateArgs{
62 Config: c,
63 Dir: dir,
64 Rel: rel,
65 File: oldFile,
66 Subdirs: subdirs,
67 RegularFiles: regularFiles,
68 GenFiles: genFiles,
69 })
70 if len(res.Empty) > 0 {
71 t.Errorf("got %d empty rules; want 0", len(res.Empty))
72 }
73 f := rule.EmptyFile("test", "")
74 for _, r := range res.Gen {
75 r.Insert(f)
76 }
77 convertImportsAttrs(f)
78 merger.FixLoads(f, lang.(language.ModuleAwareLanguage).ApparentLoads(func(string) string { return "" }))
79 f.Sync()
80 got := string(bzl.Format(f.File))
81 wantPath := filepath.Join(dir, "BUILD.want")
82 wantBytes, err := os.ReadFile(wantPath)
83 if err != nil {
84 t.Fatalf("error reading %s: %v", wantPath, err)
85 }
86 want := string(wantBytes)
87
88 if got != want {
89 t.Errorf("GenerateRules %q: got:\n%s\nwant:\n%s", rel, got, want)
90 }
91 })
92 })
93 }
94
95 func TestGenerateRulesEmpty(t *testing.T) {
96 lang := NewLanguage()
97 c := config.New()
98 c.Exts[protoName] = &ProtoConfig{}
99
100 oldContent := []byte(`
101 proto_library(
102 name = "dead_proto",
103 srcs = ["foo.proto"],
104 )
105
106 proto_library(
107 name = "live_proto",
108 srcs = ["bar.proto"],
109 )
110
111 COMPLICATED_SRCS = ["baz.proto"]
112
113 proto_library(
114 name = "complicated_proto",
115 srcs = COMPLICATED_SRCS,
116 )
117 `)
118 old, err := rule.LoadData("BUILD.bazel", "", oldContent)
119 if err != nil {
120 t.Fatal(err)
121 }
122 genFiles := []string{"bar.proto"}
123 res := lang.GenerateRules(language.GenerateArgs{
124 Config: c,
125 Rel: "foo",
126 File: old,
127 GenFiles: genFiles,
128 })
129 if len(res.Gen) > 0 {
130 t.Errorf("got %d generated rules; want 0", len(res.Gen))
131 }
132 f := rule.EmptyFile("test", "")
133 for _, r := range res.Empty {
134 r.Insert(f)
135 }
136 f.Sync()
137 got := strings.TrimSpace(string(bzl.Format(f.File)))
138 want := `proto_library(name = "dead_proto")`
139 if got != want {
140 t.Errorf("got:\n%s\nwant:\n%s", got, want)
141 }
142 }
143
144 func TestGeneratePackage(t *testing.T) {
145 if runtime.GOOS == "windows" {
146
147 if _, err := os.Stat("testdata"); os.IsNotExist(err) {
148 t.Skip("testdata missing on windows due to lack of symbolic links")
149 } else if err != nil {
150 t.Fatal(err)
151 }
152 }
153
154 lang := NewLanguage()
155 c, _, _ := testConfig(t, "testdata")
156 dir := filepath.FromSlash("testdata/protos")
157 res := lang.GenerateRules(language.GenerateArgs{
158 Config: c,
159 Dir: dir,
160 Rel: "protos",
161 RegularFiles: []string{"foo.proto"},
162 })
163 r := res.Gen[0]
164 got := r.PrivateAttr(PackageKey).(Package)
165 want := Package{
166 Name: "bar.foo",
167 Files: map[string]FileInfo{
168 "foo.proto": {
169 Path: filepath.Join(dir, "foo.proto"),
170 Name: "foo.proto",
171 PackageName: "bar.foo",
172 Options: []Option{{Key: "go_package", Value: "example.com/repo/protos"}},
173 Imports: []string{
174 "google/protobuf/any.proto",
175 "protos/sub/sub.proto",
176 },
177 HasServices: true,
178 },
179 },
180 Imports: map[string]bool{
181 "google/protobuf/any.proto": true,
182 "protos/sub/sub.proto": true,
183 },
184 Options: map[string]string{
185 "go_package": "example.com/repo/protos",
186 },
187 HasServices: true,
188 }
189 if !reflect.DeepEqual(got, want) {
190 t.Errorf("got %#v; want %#v", got, want)
191 }
192 }
193
194 func TestFileModeImports(t *testing.T) {
195 if runtime.GOOS == "windows" {
196
197 if _, err := os.Stat("testdata"); os.IsNotExist(err) {
198 t.Skip("testdata missing on windows due to lack of symbolic links")
199 } else if err != nil {
200 t.Fatal(err)
201 }
202 }
203
204 lang := NewLanguage()
205 c, _, _ := testConfig(t, "testdata")
206 c.Exts[protoName] = &ProtoConfig{
207 Mode: FileMode,
208 }
209
210 dir := filepath.FromSlash("testdata/file_mode")
211 res := lang.GenerateRules(language.GenerateArgs{
212 Config: c,
213 Dir: dir,
214 Rel: "file_mode",
215 RegularFiles: []string{"foo.proto", "bar.proto"},
216 })
217
218 if len(res.Gen) != 2 {
219 t.Error("expected 2 generated packages")
220 }
221
222 bar := res.Gen[0].PrivateAttr(PackageKey).(Package)
223 foo := res.Gen[1].PrivateAttr(PackageKey).(Package)
224
225
226 if bar.RuleName == "foo" {
227 bar, foo = foo, bar
228 }
229
230 expectedFoo := Package{
231 Name: "file_mode",
232 RuleName: "foo",
233 Files: map[string]FileInfo{
234 "foo.proto": {
235 Path: filepath.Join(dir, "foo.proto"),
236 Name: "foo.proto",
237 PackageName: "file_mode",
238 },
239 },
240 Imports: map[string]bool{},
241 Options: map[string]string{},
242 }
243
244 expectedBar := Package{
245 Name: "file_mode",
246 RuleName: "bar",
247 Files: map[string]FileInfo{
248 "bar.proto": {
249 Path: filepath.Join(dir, "bar.proto"),
250 Name: "bar.proto",
251 PackageName: "file_mode",
252 Imports: []string{
253 "file_mode/foo.proto",
254 },
255 },
256 },
257
258
259
260 Imports: map[string]bool{
261 "file_mode/foo.proto": true,
262 },
263 Options: map[string]string{},
264 }
265
266 if !reflect.DeepEqual(foo, expectedFoo) {
267 t.Errorf("got %#v; want %#v", foo, expectedFoo)
268 }
269 if !reflect.DeepEqual(bar, expectedBar) {
270 t.Errorf("got %#v; want %#v", bar, expectedBar)
271 }
272 }
273
274
275
276 func TestConsumedGenFiles(t *testing.T) {
277 if runtime.GOOS == "windows" {
278
279 if _, err := os.Stat("testdata"); os.IsNotExist(err) {
280 t.Skip("testdata missing on windows due to lack of symbolic links")
281 } else if err != nil {
282 t.Fatal(err)
283 }
284 }
285
286 oldContent := []byte(`
287 proto_library(
288 name = "existing_gen_proto",
289 srcs = ["gen.proto"],
290 )
291 proto_library(
292 name = "dead_proto",
293 srcs = ["dead.proto"],
294 )
295 `)
296 old, err := rule.LoadData("BUILD.bazel", "", oldContent)
297 if err != nil {
298 t.Fatal(err)
299 }
300
301 genRule1 := rule.NewRule("proto_library", "gen_proto")
302 genRule1.SetAttr("srcs", []string{"gen.proto"})
303 genRule2 := rule.NewRule("filegroup", "filegroup_protos")
304 genRule2.SetAttr("srcs", []string{"gen.proto", "gen_not_consumed.proto"})
305
306 c, lang, _ := testConfig(t, "testdata")
307
308 res := lang.GenerateRules(language.GenerateArgs{
309 Config: c,
310 Dir: filepath.FromSlash("testdata/protos"),
311 File: old,
312 Rel: "protos",
313 RegularFiles: []string{"foo.proto"},
314 GenFiles: []string{"gen.proto", "gen_not_consumed.proto"},
315 OtherGen: []*rule.Rule{genRule1, genRule2},
316 })
317
318
319
320
321
322
323 fg := rule.EmptyFile("test_gen", "")
324 for _, r := range res.Gen {
325 r.Insert(fg)
326 }
327 gotGen := strings.TrimSpace(string(fg.Format()))
328 wantGen := `proto_library(
329 name = "protos_proto",
330 srcs = [
331 "foo.proto",
332 "gen_not_consumed.proto",
333 ],
334 visibility = ["//visibility:public"],
335 )`
336
337 if gotGen != wantGen {
338 t.Errorf("got:\n%s\nwant:\n%s", gotGen, wantGen)
339 }
340
341
342 fe := rule.EmptyFile("test_empty", "")
343 for _, r := range res.Empty {
344 r.Insert(fe)
345 }
346 got := strings.TrimSpace(string(fe.Format()))
347 want := `proto_library(name = "dead_proto")`
348 if got != want {
349 t.Errorf("got:\n%s\nwant:\n%s", got, want)
350 }
351 }
352
353 func testConfig(t *testing.T, repoRoot string) (*config.Config, language.Language, []config.Configurer) {
354 cexts := []config.Configurer{
355 &config.CommonConfigurer{},
356 &walk.Configurer{},
357 &resolve.Configurer{},
358 }
359 lang := NewLanguage()
360 c := testtools.NewTestConfig(t, cexts, []language.Language{lang}, []string{
361 "-build_file_name=BUILD.old",
362 "-repo_root=" + repoRoot,
363 })
364 cexts = append(cexts, lang)
365 return c, lang, cexts
366 }
367
368
369
370
371 func convertImportsAttrs(f *rule.File) {
372 for _, r := range f.Rules {
373 v := r.PrivateAttr(config.GazelleImportsKey)
374 if v != nil {
375 r.SetAttr(config.GazelleImportsKey, v)
376 }
377 }
378 }
379
View as plain text