| /* Copyright 2018 The Bazel Authors. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package rule |
| |
| import ( |
| "path/filepath" |
| "reflect" |
| "sort" |
| "strings" |
| "testing" |
| |
| bzl "github.com/bazelbuild/buildtools/build" |
| ) |
| |
| // This file contains tests for File, Load, Rule, and related functions. |
| // Tests only cover some basic functionality and a few non-obvious cases. |
| // Most test coverage will come from clients of this package. |
| |
| func TestEditAndSync(t *testing.T) { |
| old := []byte(` |
| load("a.bzl", "x_library") |
| |
| x_library(name = "foo") |
| |
| load("b.bzl", y_library = "y") |
| |
| y_library(name = "bar") |
| `) |
| f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| loadA := f.Loads[0] |
| loadA.Delete() |
| loadB := f.Loads[1] |
| loadB.Add("x_library") |
| loadB.Remove("y_library") |
| loadC := NewLoad("c.bzl") |
| loadC.AddAlias("z_library", "z") |
| loadC.Add("y_library") |
| loadC.Insert(f, 3) |
| |
| foo := f.Rules[0] |
| foo.Delete() |
| bar := f.Rules[1] |
| bar.SetAttr("srcs", []string{"bar.y"}) |
| loadMaybe := NewLoad("//some:maybe.bzl") |
| loadMaybe.Add("maybe") |
| loadMaybe.Insert(f, 0) |
| baz := NewRule("maybe", "baz") |
| baz.AddArg(&bzl.LiteralExpr{Token: "z"}) |
| baz.AddArg(&bzl.LiteralExpr{Token: "z"}) |
| if err := baz.UpdateArg(0, &bzl.LiteralExpr{Token: "z0"}); err != nil { |
| t.Fatal(err) |
| } |
| if err := baz.UpdateArg(10, &bzl.LiteralExpr{Token: "blah"}); err == nil { |
| t.Fatalf("want error because tried to modify an arg outside of arg bounds, got nil") |
| } |
| baz.SetAttr("srcs", GlobValue{ |
| Patterns: []string{"**"}, |
| Excludes: []string{"*.pem"}, |
| }) |
| baz.Insert(f) |
| |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load("//some:maybe.bzl", "maybe") |
| load("b.bzl", "x_library") |
| load( |
| "c.bzl", |
| "y_library", |
| z = "z_library", |
| ) |
| |
| y_library( |
| name = "bar", |
| srcs = ["bar.y"], |
| ) |
| |
| maybe( |
| z0, |
| z, |
| name = "baz", |
| srcs = glob( |
| ["**"], |
| exclude = ["*.pem"], |
| ), |
| ) |
| `) |
| if got != want { |
| t.Errorf("got:\n%s\nwant:\n%s", got, want) |
| } |
| } |
| |
| func TestPassInserted(t *testing.T) { |
| old := []byte(` |
| load("a.bzl", "baz") |
| |
| def foo(): |
| go_repository(name = "bar") |
| `) |
| f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| f.Rules[0].Delete() |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load("a.bzl", "baz") |
| |
| def foo(): |
| pass |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestPassRemoved(t *testing.T) { |
| old := []byte(` |
| load("a.bzl", "baz") |
| |
| def foo(): |
| pass |
| `) |
| f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", old) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bar := NewRule("go_repository", "bar") |
| bar.Insert(f) |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load("a.bzl", "baz") |
| |
| def foo(): |
| go_repository(name = "bar") |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestFunctionInserted(t *testing.T) { |
| f, err := LoadMacroData(filepath.Join("old", "repo.bzl"), "", "foo", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bar := NewRule("go_repository", "bar") |
| bar.Insert(f) |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| def foo(): |
| go_repository(name = "bar") |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestArgsAlwaysEndUpBeforeKwargs(t *testing.T) { |
| f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| bar := NewRule("maybe", "bar") |
| bar.SetAttr("url", "https://doesnotexist.com") |
| bar.AddArg(&bzl.Ident{Name: "http_archive"}) |
| bar.Insert(f) |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| maybe( |
| http_archive, |
| name = "bar", |
| url = "https://doesnotexist.com", |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestDeleteSyncDelete(t *testing.T) { |
| old := []byte(` |
| x_library(name = "foo") |
| |
| # comment |
| |
| x_library(name = "bar") |
| `) |
| f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| foo := f.Rules[0] |
| bar := f.Rules[1] |
| foo.Delete() |
| f.Sync() |
| bar.Delete() |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(`# comment`) |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestInsertDeleteSync(t *testing.T) { |
| old := []byte("") |
| f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", old) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| foo := NewRule("filegroup", "test") |
| foo.Insert(f) |
| foo.Delete() |
| f.Sync() |
| got := strings.TrimSpace(string(f.Format())) |
| want := "" |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestSymbolsReturnsKeys(t *testing.T) { |
| f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(`load("a.bzl", "y", z = "a")`)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| got := f.Loads[0].Symbols() |
| want := []string{"y", "z"} |
| if !reflect.DeepEqual(got, want) { |
| t.Errorf("got %#v; want %#v", got, want) |
| } |
| } |
| |
| func TestLoadCommentsAreRetained(t *testing.T) { |
| f, err := LoadData(filepath.Join("load", "BUILD.bazel"), "", []byte(` |
| load( |
| "a.bzl", |
| # Comment for a symbol that will be deleted. |
| "baz", |
| # Some comment without remapping. |
| "foo", |
| # Some comment with remapping. |
| my_bar = "bar", |
| ) |
| `)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| l := f.Loads[0] |
| l.Remove("baz") |
| f.Sync() |
| l.Add("new_baz") |
| f.Sync() |
| |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load( |
| "a.bzl", |
| # Some comment without remapping. |
| "foo", |
| "new_baz", |
| # Some comment with remapping. |
| my_bar = "bar", |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestKeepRule(t *testing.T) { |
| for _, tc := range []struct { |
| desc, src string |
| want bool |
| }{ |
| { |
| desc: "prefix", |
| src: ` |
| # keep |
| x_library(name = "x") |
| `, |
| want: true, |
| }, { |
| desc: "prefix with description", |
| src: ` |
| # keep: hack, see more in ticket #42 |
| x_library(name = "x") |
| `, |
| want: true, |
| }, { |
| desc: "compact_suffix", |
| src: ` |
| x_library(name = "x") # keep |
| `, |
| want: true, |
| }, { |
| desc: "multiline_internal", |
| src: ` |
| x_library( # keep |
| name = "x", |
| ) |
| `, |
| want: false, |
| }, { |
| desc: "multiline_suffix", |
| src: ` |
| x_library( |
| name = "x", |
| ) # keep |
| `, |
| want: true, |
| }, { |
| desc: "after", |
| src: ` |
| x_library(name = "x") |
| # keep |
| `, |
| want: false, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| f, err := LoadData(filepath.Join(tc.desc, "BUILD.bazel"), "", []byte(tc.src)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if got := f.Rules[0].ShouldKeep(); got != tc.want { |
| t.Errorf("got %v; want %v", got, tc.want) |
| } |
| }) |
| } |
| } |
| |
| func TestShouldKeepExpr(t *testing.T) { |
| for _, tc := range []struct { |
| desc, src string |
| path func(e bzl.Expr) bzl.Expr |
| want bool |
| }{ |
| { |
| desc: "before", |
| src: ` |
| # keep |
| "s" |
| `, |
| want: true, |
| }, { |
| desc: "before with description", |
| src: ` |
| # keep: we need it for the ninja feature |
| "s" |
| `, |
| want: true, |
| }, { |
| desc: "before but not the correct prefix (keeping)", |
| src: ` |
| # keeping this for now |
| "s" |
| `, |
| want: false, |
| }, { |
| desc: "before but not the correct prefix (no colon)", |
| src: ` |
| # keep this around for the time being |
| "s" |
| `, |
| want: false, |
| }, { |
| desc: "suffix", |
| src: ` |
| "s" # keep |
| `, |
| want: true, |
| }, { |
| desc: "after", |
| src: ` |
| "s" |
| # keep |
| `, |
| want: false, |
| }, { |
| desc: "list_elem_prefix", |
| src: ` |
| [ |
| # keep |
| "s", |
| ] |
| `, |
| path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] }, |
| want: true, |
| }, { |
| desc: "list_elem_suffix", |
| src: ` |
| [ |
| "s", # keep |
| ] |
| `, |
| path: func(e bzl.Expr) bzl.Expr { return e.(*bzl.ListExpr).List[0] }, |
| want: true, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| ast, err := bzl.Parse(tc.desc, []byte(tc.src)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| expr := ast.Stmt[0] |
| if tc.path != nil { |
| expr = tc.path(expr) |
| } |
| got := ShouldKeep(expr) |
| if got != tc.want { |
| t.Errorf("got %v; want %v", got, tc.want) |
| } |
| }) |
| } |
| } |
| |
| func TestInternalVisibility(t *testing.T) { |
| tests := []struct { |
| rel string |
| expected string |
| }{ |
| {rel: "internal", expected: "//:__subpackages__"}, |
| {rel: "a/b/internal", expected: "//a/b:__subpackages__"}, |
| {rel: "a/b/internal/c", expected: "//a/b:__subpackages__"}, |
| {rel: "a/b/internal/c/d", expected: "//a/b:__subpackages__"}, |
| {rel: "a/b/internal/c/internal", expected: "//a/b/internal/c:__subpackages__"}, |
| {rel: "a/b/internal/c/internal/d", expected: "//a/b/internal/c:__subpackages__"}, |
| } |
| |
| for _, tt := range tests { |
| if actual := CheckInternalVisibility(tt.rel, "default"); actual != tt.expected { |
| t.Errorf("got %v; want %v", actual, tt.expected) |
| } |
| } |
| } |
| |
| func TestSortLoadsByName(t *testing.T) { |
| f, err := LoadMacroData( |
| filepath.Join("third_party", "repos.bzl"), |
| "", "repos", |
| []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository") |
| load("//some:maybe.bzl", "maybe") |
| load("//some2:maybe.bzl", "maybe2") |
| load("//some1:maybe4.bzl", "maybe1") |
| load("b.bzl", "x_library") |
| def repos(): |
| go_repository( |
| name = "com_github_bazelbuild_bazel_gazelle", |
| ) |
| `)) |
| if err != nil { |
| t.Error(err) |
| } |
| sort.Stable(loadsByName{ |
| loads: f.Loads, |
| exprs: f.File.Stmt, |
| }) |
| f.Sync() |
| |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load("@bazel_gazelle//:deps.bzl", "go_repository") |
| load("//some:maybe.bzl", "maybe") |
| load("//some1:maybe4.bzl", "maybe1") |
| load("//some2:maybe.bzl", "maybe2") |
| load("b.bzl", "x_library") |
| |
| def repos(): |
| go_repository( |
| name = "com_github_bazelbuild_bazel_gazelle", |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:\n%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestSortRulesByKindAndName(t *testing.T) { |
| f, err := LoadMacroData( |
| filepath.Join("third_party", "repos.bzl"), |
| "", "repos", |
| []byte(`load("@bazel_gazelle//:deps.bzl", "go_repository") |
| def repos(): |
| some_other_call_rule() |
| go_repository( |
| name = "com_github_andybalholm_cascadia", |
| ) |
| go_repository( |
| name = "com_github_bazelbuild_buildtools", |
| ) |
| go_repository( |
| name = "com_github_bazelbuild_rules_go", |
| ) |
| go_repository( |
| name = "com_github_bazelbuild_bazel_gazelle", |
| ) |
| a_rule( |
| name = "name1", |
| ) |
| `)) |
| if err != nil { |
| t.Error(err) |
| } |
| sort.Stable(rulesByKindAndName{ |
| rules: f.Rules, |
| exprs: f.function.stmt.Body, |
| }) |
| repos := []string{ |
| "name1", |
| "com_github_andybalholm_cascadia", |
| "com_github_bazelbuild_bazel_gazelle", |
| "com_github_bazelbuild_buildtools", |
| "com_github_bazelbuild_rules_go", |
| } |
| for i, r := range repos { |
| rule := f.Rules[i] |
| if rule.Name() != r { |
| t.Errorf("expect rule %s at %d, got %s", r, i, rule.Name()) |
| } |
| if rule.Index() != i { |
| t.Errorf("expect rule %s with index %d, got %d", r, i, rule.Index()) |
| } |
| if f.function.stmt.Body[i] != rule.expr { |
| t.Errorf("underlying syntax tree of rule %s not sorted", r) |
| } |
| } |
| |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| load("@bazel_gazelle//:deps.bzl", "go_repository") |
| |
| def repos(): |
| a_rule( |
| name = "name1", |
| ) |
| go_repository( |
| name = "com_github_andybalholm_cascadia", |
| ) |
| |
| go_repository( |
| name = "com_github_bazelbuild_bazel_gazelle", |
| ) |
| go_repository( |
| name = "com_github_bazelbuild_buildtools", |
| ) |
| go_repository( |
| name = "com_github_bazelbuild_rules_go", |
| ) |
| some_other_call_rule() |
| `) |
| |
| if got != want { |
| t.Errorf("got:%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestCheckFile(t *testing.T) { |
| f := File{Rules: []*Rule{ |
| NewRule("go_repository", "com_google_cloud_go_pubsub"), |
| NewRule("go_repository", "com_google_cloud_go_pubsub"), |
| }} |
| err := checkFile(&f) |
| if err == nil { |
| t.Errorf("muliple rules with the same name should not be tolerated") |
| } |
| |
| f = File{Rules: []*Rule{ |
| NewRule("go_rules_dependencies", ""), |
| NewRule("go_register_toolchains", ""), |
| }} |
| err = checkFile(&f) |
| if err != nil { |
| t.Errorf("unexpected error: %v", err) |
| } |
| } |
| |
| func TestAttributeComment(t *testing.T) { |
| f, err := LoadData(filepath.Join("old", "BUILD.bazel"), "", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r := NewRule("a_rule", "name1") |
| r.SetAttr("deps", []string{"foo", "bar", "baz"}) |
| r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) |
| hdrComments := r.AttrComments("hdrs") |
| hdrComments.Before = append(hdrComments.Before, bzl.Comment{ |
| Token: "# do not sort", |
| }) |
| |
| r.Insert(f) |
| |
| got := strings.TrimSpace(string(f.Format())) |
| want := strings.TrimSpace(` |
| a_rule( |
| name = "name1", |
| # do not sort |
| hdrs = [ |
| "foo", |
| "bar", |
| "baz", |
| ], |
| deps = [ |
| "bar", |
| "baz", |
| "foo", |
| ], |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestSimpleArgument(t *testing.T) { |
| f := EmptyFile("foo", "bar") |
| |
| r := NewRule("export_files", "") |
| r.AddArg(&bzl.CallExpr{ |
| X: &bzl.Ident{Name: "glob"}, |
| List: []bzl.Expr{ |
| &bzl.ListExpr{ |
| List: []bzl.Expr{ |
| &bzl.StringExpr{Value: "**"}, |
| }, |
| }, |
| }, |
| }) |
| |
| r.Insert(f) |
| f.Sync() |
| |
| got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) |
| want := strings.TrimSpace(` |
| export_files(glob(["**"])) |
| `) |
| |
| if got != want { |
| t.Errorf("got:%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestAttributeValueSorting(t *testing.T) { |
| f := EmptyFile("foo", "bar") |
| |
| r := NewRule("a_rule", "") |
| r.SetAttr("deps", []string{"foo", "bar", "baz"}) |
| r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"}) |
| r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) |
| |
| r.Insert(f) |
| f.Sync() |
| |
| got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) |
| want := strings.TrimSpace(` |
| a_rule( |
| srcs = [ |
| "foo", |
| "bar", |
| "baz", |
| ], |
| hdrs = [ |
| "foo", |
| "bar", |
| "baz", |
| ], |
| deps = [ |
| "bar", |
| "baz", |
| "foo", |
| ], |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:%s\nwant:%s", got, want) |
| } |
| } |
| |
| func TestAttributeValueSortingOverride(t *testing.T) { |
| f := EmptyFile("foo", "bar") |
| |
| r := NewRule("a_rule", "") |
| r.SetAttr("deps", []string{"foo", "bar", "baz"}) |
| r.SetAttr("srcs", UnsortedStrings{"foo", "bar", "baz"}) |
| r.SetAttr("hdrs", []string{"foo", "bar", "baz"}) |
| r.SetSortedAttrs([]string{"srcs", "hdrs"}) |
| |
| r.Insert(f) |
| f.Sync() |
| |
| got := strings.TrimSpace(string(bzl.FormatWithoutRewriting(f.File))) |
| want := strings.TrimSpace(` |
| a_rule( |
| srcs = [ |
| "foo", |
| "bar", |
| "baz", |
| ], |
| hdrs = [ |
| "bar", |
| "baz", |
| "foo", |
| ], |
| deps = [ |
| "foo", |
| "bar", |
| "baz", |
| ], |
| ) |
| `) |
| |
| if got != want { |
| t.Errorf("got:%s\nwant:%s", got, want) |
| } |
| |
| if !reflect.DeepEqual(r.SortedAttrs(), []string{"srcs", "hdrs"}) { |
| t.Errorf("Unexpected r.SortedAttrs(): %v", r.SortedAttrs()) |
| } |
| } |