...

Source file src/github.com/bazelbuild/bazel-gazelle/rule/rule_test.go

Documentation: github.com/bazelbuild/bazel-gazelle/rule

     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 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  // This file contains tests for File, Load, Rule, and related functions.
    29  // Tests only cover some basic functionality and a few non-obvious cases.
    30  // Most test coverage will come from clients of this package.
    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