1
15
16 package rule
17
18 import (
19 "path/filepath"
20 "reflect"
21 "sort"
22 "strings"
23 "testing"
24
25 bzl "github.com/bazelbuild/buildtools/build"
26 )
27
28
29
30
31
32 func TestEditAndSync(t *testing.T) {
33 old := []byte(`
34 load("a.bzl", "x_library")
35
36 x_library(name = "foo")
37
38 load("b.bzl", y_library = "y")
39
40 y_library(name = "bar")
41 `)
42 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old)
43 if err != nil {
44 t.Fatal(err)
45 }
46
47 loadA := f.Loads[0]
48 loadA.Delete()
49 loadB := f.Loads[1]
50 loadB.Add("x_library")
51 loadB.Remove("y_library")
52 loadC := NewLoad("c.bzl")
53 loadC.AddAlias("z_library", "z")
54 loadC.Add("y_library")
55 loadC.Insert(f, 3)
56
57 foo := f.Rules[0]
58 foo.Delete()
59 bar := f.Rules[1]
60 bar.SetAttr("srcs", []string{"bar.y"})
61 loadMaybe := NewLoad("//some:maybe.bzl")
62 loadMaybe.Add("maybe")
63 loadMaybe.Insert(f, 0)
64 baz := NewRule("maybe", "baz")
65 baz.AddArg(&bzl.LiteralExpr{Token: "z"})
66 baz.SetAttr("srcs", GlobValue{
67 Patterns: []string{"**"},
68 Excludes: []string{"*.pem"},
69 })
70 baz.Insert(f)
71
72 got := strings.TrimSpace(string(f.Format()))
73 want := strings.TrimSpace(`
74 load("//some:maybe.bzl", "maybe")
75 load("b.bzl", "x_library")
76 load(
77 "c.bzl",
78 "y_library",
79 z = "z_library",
80 )
81
82 y_library(
83 name = "bar",
84 srcs = ["bar.y"],
85 )
86
87 maybe(
88 z,
89 name = "baz",
90 srcs = glob(
91 ["**"],
92 exclude = ["*.pem"],
93 ),
94 )
95 `)
96 if got != want {
97 t.Errorf("got:\n%s\nwant:\n%s", got, want)
98 }
99 }
100
101 func TestPassInserted(t *testing.T) {
102 old := []byte(`
103 load("a.bzl", "baz")
104
105 def foo():
106 go_repository(name = "bar")
107 `)
108 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old)
109 if err != nil {
110 t.Fatal(err)
111 }
112
113 f.Rules[0].Delete()
114 f.Sync()
115 got := strings.TrimSpace(string(f.Format()))
116 want := strings.TrimSpace(`
117 load("a.bzl", "baz")
118
119 def foo():
120 pass
121 `)
122
123 if got != want {
124 t.Errorf("got:\n%s\nwant:%s", got, want)
125 }
126 }
127
128 func TestPassRemoved(t *testing.T) {
129 old := []byte(`
130 load("a.bzl", "baz")
131
132 def foo():
133 pass
134 `)
135 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old)
136 if err != nil {
137 t.Fatal(err)
138 }
139
140 bar := NewRule("go_repository", "bar")
141 bar.Insert(f)
142 f.Sync()
143 got := strings.TrimSpace(string(f.Format()))
144 want := strings.TrimSpace(`
145 load("a.bzl", "baz")
146
147 def foo():
148 go_repository(name = "bar")
149 `)
150
151 if got != want {
152 t.Errorf("got:\n%s\nwant:%s", got, want)
153 }
154 }
155
156 func TestFunctionInserted(t *testing.T) {
157 f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", nil)
158 if err != nil {
159 t.Fatal(err)
160 }
161
162 bar := NewRule("go_repository", "bar")
163 bar.Insert(f)
164 f.Sync()
165 got := strings.TrimSpace(string(f.Format()))
166 want := strings.TrimSpace(`
167 def foo():
168 go_repository(name = "bar")
169 `)
170
171 if got != want {
172 t.Errorf("got:\n%s\nwant:%s", got, want)
173 }
174 }
175
176 func TestArgsAlwaysEndUpBeforeKwargs(t *testing.T) {
177 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil)
178 if err != nil {
179 t.Fatal(err)
180 }
181
182 bar := NewRule("maybe", "bar")
183 bar.SetAttr("url", "https://doesnotexist.com")
184 bar.AddArg(&bzl.Ident{Name: "http_archive"})
185 bar.Insert(f)
186 f.Sync()
187 got := strings.TrimSpace(string(f.Format()))
188 want := strings.TrimSpace(`
189 maybe(
190 http_archive,
191 name = "bar",
192 url = "https://doesnotexist.com",
193 )
194 `)
195
196 if got != want {
197 t.Errorf("got:\n%s\nwant:%s", got, want)
198 }
199 }
200
201 func TestDeleteSyncDelete(t *testing.T) {
202 old := []byte(`
203 x_library(name = "foo")
204
205 # comment
206
207 x_library(name = "bar")
208 `)
209 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old)
210 if err != nil {
211 t.Fatal(err)
212 }
213
214 foo := f.Rules[0]
215 bar := f.Rules[1]
216 foo.Delete()
217 f.Sync()
218 bar.Delete()
219 f.Sync()
220 got := strings.TrimSpace(string(f.Format()))
221 want := strings.TrimSpace(`# comment`)
222 if got != want {
223 t.Errorf("got:\n%s\nwant:%s", got, want)
224 }
225 }
226
227 func TestInsertDeleteSync(t *testing.T) {
228 old := []byte("")
229 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old)
230 if err != nil {
231 t.Fatal(err)
232 }
233
234 foo := NewRule("filegroup", "test")
235 foo.Insert(f)
236 foo.Delete()
237 f.Sync()
238 got := strings.TrimSpace(string(f.Format()))
239 want := ""
240 if got != want {
241 t.Errorf("got:\n%s\nwant:%s", got, want)
242 }
243 }
244
245 func TestSymbolsReturnsKeys(t *testing.T) {
246 f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(`load("a.bzl", "y", z = "a")`))
247 if err != nil {
248 t.Fatal(err)
249 }
250 got := f.Loads[0].Symbols()
251 want := []string{"y", "z"}
252 if !reflect.DeepEqual(got, want) {
253 t.Errorf("got %#v; want %#v", got, want)
254 }
255 }
256
257 func TestLoadCommentsAreRetained(t *testing.T) {
258 f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(`
259 load(
260 "a.bzl",
261 # Comment for a symbol that will be deleted.
262 "baz",
263 # Some comment without remapping.
264 "foo",
265 # Some comment with remapping.
266 my_bar = "bar",
267 )
268 `))
269 if err != nil {
270 t.Fatal(err)
271 }
272 l := f.Loads[0]
273 l.Remove("baz")
274 f.Sync()
275 l.Add("new_baz")
276 f.Sync()
277
278 got := strings.TrimSpace(string(f.Format()))
279 want := strings.TrimSpace(`
280 load(
281 "a.bzl",
282 # Some comment without remapping.
283 "foo",
284 "new_baz",
285 # Some comment with remapping.
286 my_bar = "bar",
287 )
288 `)
289
290 if got != want {
291 t.Errorf("got:\n%s\nwant:%s", got, want)
292 }
293 }
294
295 func TestKeepRule(t *testing.T) {
296 for _, tc := range []struct {
297 desc, src string
298 want bool
299 }{
300 {
301 desc: "prefix",
302 src: `
303 # keep
304 x_library(name = "x")
305 `,
306 want: true,
307 }, {
308 desc: "prefix with description",
309 src: `
310 # keep: hack, see more in ticket #42
311 x_library(name = "x")
312 `,
313 want: true,
314 }, {
315 desc: "compact_suffix",
316 src: `
317 x_library(name = "x") # keep
318 `,
319 want: true,
320 }, {
321 desc: "multiline_internal",
322 src: `
323 x_library( # keep
324 name = "x",
325 )
326 `,
327 want: false,
328 }, {
329 desc: "multiline_suffix",
330 src: `
331 x_library(
332 name = "x",
333 ) # keep
334 `,
335 want: true,
336 }, {
337 desc: "after",
338 src: `
339 x_library(name = "x")
340 # keep
341 `,
342 want: false,
343 },
344 } {
345 t.Run(tc.desc, func(t *testing.T) {
346 f, err := LoadData(filepath.Join(tc.desc, "BUILD.bazel"), "", []byte(tc.src))
347 if err != nil {
348 t.Fatal(err)
349 }
350 if got := f.Rules[0].ShouldKeep(); got != tc.want {
351 t.Errorf("got %v; want %v", got, tc.want)
352 }
353 })
354 }
355 }
356
357 func TestShouldKeepExpr(t *testing.T) {
358 for _, tc := range []struct {
359 desc, src string
360 path func(e bzl.Expr) bzl.Expr
361 want bool
362 }{
363 {
364 desc: "before",
365 src: `
366 # keep
367 "s"
368 `,
369 want: true,
370 }, {
371 desc: "before with description",
372 src: `
373 # keep: we need it for the ninja feature
374 "s"
375 `,
376 want: true,
377 }, {
378 desc: "before but not the correct prefix (keeping)",
379 src: `
380 # keeping this for now
381 "s"
382 `,
383 want: false,
384 }, {
385 desc: "before but not the correct prefix (no colon)",
386 src: `
387 # keep this around for the time being
388 "s"
389 `,
390 want: false,
391 }, {
392 desc: "suffix",
393 src: `
394 "s" # keep
395 `,
396 want: true,
397 }, {
398 desc: "after",
399 src: `
400 "s"
401 # keep
402 `,
403 want: false,
404 }, {
405 desc: "list_elem_prefix",
406 src: `
407 [
408 # keep
409 "s",
410 ]
411 `,
412 path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] },
413 want: true,
414 }, {
415 desc: "list_elem_suffix",
416 src: `
417 [
418 "s", # keep
419 ]
420 `,
421 path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] },
422 want: true,
423 },
424 } {
425 t.Run(tc.desc, func(t *testing.T) {
426 ast, err := bzl.Parse(tc.desc, []byte(tc.src))
427 if err != nil {
428 t.Fatal(err)
429 }
430 expr := ast.Stmt[0]
431 if tc.path != nil {
432 expr = tc.path(expr)
433 }
434 got := ShouldKeep(expr)
435 if got != tc.want {
436 t.Errorf("got %v; want %v", got, tc.want)
437 }
438 })
439 }
440 }
441
442 func TestInternalVisibility(t *testing.T) {
443 tests := []struct {
444 rel string
445 expected string
446 }{
447 {rel: "internal", expected: "//:__subpackages__"},
448 {rel: "a/b/internal", expected: "//a/b:__subpackages__"},
449 {rel: "a/b/internal/c", expected: "//a/b:__subpackages__"},
450 {rel: "a/b/internal/c/d", expected: "//a/b:__subpackages__"},
451 {rel: "a/b/internal/c/internal", expected: "//a/b/internal/c:__subpackages__"},
452 {rel: "a/b/internal/c/internal/d", expected: "//a/b/internal/c:__subpackages__"},
453 }
454
455 for _, tt := range tests {
456 if actual := CheckInternalVisibility(tt.rel, "default"); actual != tt.expected {
457 t.Errorf("got %v; want %v", actual, tt.expected)
458 }
459 }
460 }
461
462 func TestSortLoadsByName(t *testing.T) {
463 f, err := LoadMacroData(
464 filepath.Join("third_party", "repos.bzl"),
465 "", "repos",
466 []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository")
467 load("//some:maybe.bzl", "maybe")
468 load("//some2:maybe.bzl", "maybe2")
469 load("//some1:maybe4.bzl", "maybe1")
470 load("b.bzl", "x_library")
471 def repos():
472 go_repository(
473 name = "com_github_bazelbuild_bazel_gazelle",
474 )
475 `))
476 if err != nil {
477 t.Error(err)
478 }
479 sort.Stable(loadsByName{
480 loads: f.Loads,
481 exprs: f.File.Stmt,
482 })
483 f.Sync()
484
485 got := strings.TrimSpace(string(f.Format()))
486 want := strings.TrimSpace(`
487 load("@bazel_gazelle//:deps.bzl", "go_repository")
488 load("//some:maybe.bzl", "maybe")
489 load("//some1:maybe4.bzl", "maybe1")
490 load("//some2:maybe.bzl", "maybe2")
491 load("b.bzl", "x_library")
492
493 def repos():
494 go_repository(
495 name = "com_github_bazelbuild_bazel_gazelle",
496 )
497 `)
498
499 if got != want {
500 t.Errorf("got:\n%s\nwant:%s", got, want)
501 }
502 }
503
504 func TestSortRulesByKindAndName(t *testing.T) {
505 f, err := LoadMacroData(
506 filepath.Join("third_party", "repos.bzl"),
507 "", "repos",
508 []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository")
509 def repos():
510 some_other_call_rule()
511 go_repository(
512 name = "com_github_andybalholm_cascadia",
513 )
514 go_repository(
515 name = "com_github_bazelbuild_buildtools",
516 )
517 go_repository(
518 name = "com_github_bazelbuild_rules_go",
519 )
520 go_repository(
521 name = "com_github_bazelbuild_bazel_gazelle",
522 )
523 a_rule(
524 name = "name1",
525 )
526 `))
527 if err != nil {
528 t.Error(err)
529 }
530 sort.Stable(rulesByKindAndName{
531 rules: f.Rules,
532 exprs: f.function.stmt.Body,
533 })
534 repos := []string{
535 "name1",
536 "com_github_andybalholm_cascadia",
537 "com_github_bazelbuild_bazel_gazelle",
538 "com_github_bazelbuild_buildtools",
539 "com_github_bazelbuild_rules_go",
540 }
541 for i, r := range repos {
542 rule := f.Rules[i]
543 if rule.Name() != r {
544 t.Errorf("expect rule %s at %d, got %s", r, i, rule.Name())
545 }
546 if rule.Index() != i {
547 t.Errorf("expect rule %s with index %d, got %d", r, i, rule.Index())
548 }
549 if f.function.stmt.Body[i] != rule.expr {
550 t.Errorf("underlying syntax tree of rule %s not sorted", r)
551 }
552 }
553
554 got := strings.TrimSpace(string(f.Format()))
555 want := strings.TrimSpace(`
556 load("@bazel_gazelle//:deps.bzl", "go_repository")
557
558 def repos():
559 a_rule(
560 name = "name1",
561 )
562 go_repository(
563 name = "com_github_andybalholm_cascadia",
564 )
565
566 go_repository(
567 name = "com_github_bazelbuild_bazel_gazelle",
568 )
569 go_repository(
570 name = "com_github_bazelbuild_buildtools",
571 )
572 go_repository(
573 name = "com_github_bazelbuild_rules_go",
574 )
575 some_other_call_rule()
576 `)
577
578 if got != want {
579 t.Errorf("got:%s\nwant:%s", got, want)
580 }
581 }
582
583 func TestCheckFile(t *testing.T) {
584 f := File{Rules: []*Rule{
585 NewRule("go_repository", "com_google_cloud_go_pubsub"),
586 NewRule("go_repository", "com_google_cloud_go_pubsub"),
587 }}
588 err := checkFile(&f)
589 if err == nil {
590 t.Errorf("muliple rules with the same name should not be tolerated")
591 }
592
593 f = File{Rules: []*Rule{
594 NewRule("go_rules_dependencies", ""),
595 NewRule("go_register_toolchains", ""),
596 }}
597 err = checkFile(&f)
598 if err != nil {
599 t.Errorf("unexpected error: %v", err)
600 }
601 }
602
603 func TestAttributeComment(t *testing.T) {
604 f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil)
605 if err != nil {
606 t.Fatal(err)
607 }
608
609 r := NewRule("a_rule", "name1")
610 r.SetAttr("deps", []string{"foo", "bar", "baz"})
611 r.SetAttr("hdrs", []string{"foo", "bar", "baz"})
612 hdrComments := r.AttrComments("hdrs")
613 hdrComments.Before = append(hdrComments.Before, bzl.Comment{
614 Token: "# do not sort",
615 })
616
617 r.Insert(f)
618
619 got := strings.TrimSpace(string(f.Format()))
620 want := strings.TrimSpace(`
621 a_rule(
622 name = "name1",
623 # do not sort
624 hdrs = [
625 "foo",
626 "bar",
627 "baz",
628 ],
629 deps = [
630 "bar",
631 "baz",
632 "foo",
633 ],
634 )
635 `)
636
637 if got != want {
638 t.Errorf("got:%s\nwant:%s", got, want)
639 }
640 }
641
642 func TestSimpleArgument(t *testing.T) {
643 f := EmptyFile("foo", "bar")
644
645 r := NewRule("export_files", "")
646 r.AddArg(&bzl.CallExpr{
647 X: &bzl.Ident{Name: "glob"},
648 List: []bzl.Expr{
649 &bzl.ListExpr{
650 List: []bzl.Expr{
651 &bzl.StringExpr{Value: "**"},
652 },
653 },
654 },
655 })
656
657 r.Insert(f)
658 f.Sync()
659
660 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File)))
661 want := strings.TrimSpace(`
662 export_files(glob(["**"]))
663 `)
664
665 if got != want {
666 t.Errorf("got:%s\nwant:%s", got, want)
667 }
668 }
669
670 func TestAttributeValueSorting(t *testing.T) {
671 f := EmptyFile("foo", "bar")
672
673 r := NewRule("a_rule", "")
674 r.SetAttr("deps", []string{"foo", "bar", "baz"})
675 r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"})
676 r.SetAttr("hdrs", []string{"foo", "bar", "baz"})
677
678 r.Insert(f)
679 f.Sync()
680
681 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File)))
682 want := strings.TrimSpace(`
683 a_rule(
684 srcs = [
685 "foo",
686 "bar",
687 "baz",
688 ],
689 hdrs = [
690 "foo",
691 "bar",
692 "baz",
693 ],
694 deps = [
695 "bar",
696 "baz",
697 "foo",
698 ],
699 )
700 `)
701
702 if got != want {
703 t.Errorf("got:%s\nwant:%s", got, want)
704 }
705 }
706
707 func TestAttributeValueSortingOverride(t *testing.T) {
708 f := EmptyFile("foo", "bar")
709
710 r := NewRule("a_rule", "")
711 r.SetAttr("deps", []string{"foo", "bar", "baz"})
712 r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"})
713 r.SetAttr("hdrs", []string{"foo", "bar", "baz"})
714 r.SetSortedAttrs([]string{"srcs", "hdrs"})
715
716 r.Insert(f)
717 f.Sync()
718
719 got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File)))
720 want := strings.TrimSpace(`
721 a_rule(
722 srcs = [
723 "foo",
724 "bar",
725 "baz",
726 ],
727 hdrs = [
728 "bar",
729 "baz",
730 "foo",
731 ],
732 deps = [
733 "foo",
734 "bar",
735 "baz",
736 ],
737 )
738 `)
739
740 if got != want {
741 t.Errorf("got:%s\nwant:%s", got, want)
742 }
743
744 if !reflect.DeepEqual(r.SortedAttrs(), []string{"srcs", "hdrs"}) {
745 t.Errorf("Unexpected r.SortedAttrs(): %v", r.SortedAttrs())
746 }
747 }
748
View as plain text