...

Source file src/github.com/bazelbuild/buildtools/edit/buildozer_test.go

Documentation: github.com/bazelbuild/buildtools/edit

     1  /*
     2  Copyright 2019 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package edit
    18  
    19  import (
    20  	"io/ioutil"
    21  	"os"
    22  	"path/filepath"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/bazelbuild/buildtools/build"
    28  )
    29  
    30  var removeCommentTests = []struct {
    31  	args      []string
    32  	buildFile string
    33  	expected  string
    34  }{
    35  	{[]string{},
    36  		`# comment
    37  	foo(
    38  		name = "foo",
    39  	)`,
    40  		`foo(
    41      name = "foo",
    42  )`,
    43  	},
    44  	{[]string{
    45  		"name",
    46  	},
    47  		`foo(
    48  		# comment
    49  		name = "foo",
    50  	)`,
    51  		`foo(
    52      name = "foo",
    53  )`,
    54  	},
    55  	{[]string{
    56  		"name",
    57  	},
    58  		`foo(
    59  		name = "foo"  # comment,
    60  	)`,
    61  		`foo(
    62      name = "foo",
    63  )`,
    64  	},
    65  	{[]string{
    66  		"deps", "bar",
    67  	},
    68  		`foo(
    69  		name = "foo",
    70  		deps = [
    71  				# comment
    72  				"bar",
    73  				"baz",
    74  		],
    75  	)`,
    76  		`foo(
    77      name = "foo",
    78      deps = [
    79          "bar",
    80          "baz",
    81      ],
    82  )`,
    83  	},
    84  	{[]string{
    85  		"deps", "bar",
    86  	},
    87  		`foo(
    88  		name = "foo",
    89  		deps = [
    90  				"bar",  # comment
    91  				"baz",
    92  		],
    93  	)`,
    94  		`foo(
    95      name = "foo",
    96      deps = [
    97          "bar",
    98          "baz",
    99      ],
   100  )`,
   101  	},
   102  }
   103  
   104  func TestCmdRemoveComment(t *testing.T) {
   105  	for i, tt := range removeCommentTests {
   106  		bld, err := build.Parse("BUILD", []byte(tt.buildFile))
   107  		if err != nil {
   108  			t.Error(err)
   109  			continue
   110  		}
   111  		rl := bld.Rules("foo")[0]
   112  		env := CmdEnvironment{
   113  			File: bld,
   114  			Rule: rl,
   115  			Args: tt.args,
   116  		}
   117  		bld, _ = cmdRemoveComment(NewOpts(), env)
   118  		got := strings.TrimSpace(string(build.Format(bld)))
   119  		if got != tt.expected {
   120  			t.Errorf("cmdRemoveComment(%d):\ngot:\n%s\nexpected:\n%s", i, got, tt.expected)
   121  		}
   122  	}
   123  }
   124  
   125  type targetExpressionToBuildFilesTestCase struct {
   126  	rootDir, target string
   127  	buildFiles      []string
   128  }
   129  
   130  func setupTestTmpWorkspace(t *testing.T, buildFileName string) (tmp string) {
   131  	tmp, err := ioutil.TempDir("", "")
   132  	if err != nil {
   133  		t.Fatal(err)
   134  	}
   135  
   136  	// On MacOS "/tmp" is a symlink to "/private/tmp". Resolve it to make the testing easier
   137  	tmp, err = filepath.EvalSymlinks(tmp)
   138  	if err != nil {
   139  		t.Fatal(err)
   140  	}
   141  
   142  	if err := os.MkdirAll(filepath.Join(tmp, "a", "b"), 0755); err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	if err := os.MkdirAll(filepath.Join(tmp, "a", "c"), 0755); err != nil {
   146  		t.Fatal(err)
   147  	}
   148  	if err := ioutil.WriteFile(filepath.Join(tmp, "WORKSPACE"), nil, 0755); err != nil {
   149  		t.Fatal(err)
   150  	}
   151  	if err := ioutil.WriteFile(filepath.Join(tmp, buildFileName), nil, 0755); err != nil {
   152  		t.Fatal(err)
   153  	}
   154  	if err := ioutil.WriteFile(filepath.Join(tmp, "a", buildFileName), nil, 0755); err != nil {
   155  		t.Fatal(err)
   156  	}
   157  	if err := ioutil.WriteFile(filepath.Join(tmp, "a", "b", buildFileName), nil, 0755); err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	if err := ioutil.WriteFile(filepath.Join(tmp, "a", "c", buildFileName), nil, 0755); err != nil {
   161  		t.Fatal(err)
   162  	}
   163  	return
   164  }
   165  
   166  func runTestTargetExpressionToBuildFiles(t *testing.T, buildFileName string) {
   167  	tmp := setupTestTmpWorkspace(t, buildFileName)
   168  	defer os.RemoveAll(tmp)
   169  
   170  	for _, tc := range []targetExpressionToBuildFilesTestCase{
   171  		{tmp, "//", []string{filepath.Join(tmp, buildFileName)}},
   172  		{tmp, "//:foo", []string{filepath.Join(tmp, buildFileName)}},
   173  		{tmp, "//a", []string{filepath.Join(tmp, "a", buildFileName)}},
   174  		{tmp, "//a:foo", []string{filepath.Join(tmp, "a", buildFileName)}},
   175  		{tmp, "//a/b", []string{filepath.Join(tmp, "a", "b", buildFileName)}},
   176  		{tmp, "//a/b:foo", []string{filepath.Join(tmp, "a", "b", buildFileName)}},
   177  		{tmp, "//...", []string{filepath.Join(tmp, buildFileName), filepath.Join(tmp, "a", buildFileName), filepath.Join(tmp, "a", "b", buildFileName), filepath.Join(tmp, "a", "c", buildFileName)}},
   178  		{tmp, "//a/...", []string{filepath.Join(tmp, "a", buildFileName), filepath.Join(tmp, "a", "b", buildFileName), filepath.Join(tmp, "a", "c", buildFileName)}},
   179  		{tmp, "//a/b/...", []string{filepath.Join(tmp, "a", "b", buildFileName)}},
   180  		{tmp, "//a/c/...", []string{filepath.Join(tmp, "a", "c", buildFileName)}},
   181  		{tmp, "//a/c/...:foo", []string{filepath.Join(tmp, "a", "c", buildFileName)}},
   182  		{"", "...:foo", []string{filepath.Join(tmp, buildFileName), filepath.Join(tmp, "a", buildFileName), filepath.Join(tmp, "a", "b", buildFileName), filepath.Join(tmp, "a", "c", buildFileName)}},
   183  	} {
   184  		if tc.rootDir == "" {
   185  			cwd, err := os.Getwd()
   186  			if err != nil {
   187  				t.Fatal(err)
   188  			}
   189  			// buildozer should be able to find the WORKSPACE file in the current wd
   190  			if err := os.Chdir(tmp); err != nil {
   191  				t.Fatal(err)
   192  			}
   193  			defer os.Chdir(cwd)
   194  		}
   195  
   196  		buildFiles := targetExpressionToBuildFiles(tc.rootDir, tc.target)
   197  		expectedBuildFilesMap := make(map[string]bool)
   198  		buildFilesMap := make(map[string]bool)
   199  		for _, buildFile := range buildFiles {
   200  			buildFilesMap[buildFile] = true
   201  		}
   202  		for _, buildFile := range tc.buildFiles {
   203  			expectedBuildFilesMap[buildFile] = true
   204  		}
   205  		if !reflect.DeepEqual(expectedBuildFilesMap, buildFilesMap) {
   206  			t.Errorf("TargetExpressionToBuildFiles(%q, %q) = %q want %q", tc.rootDir, tc.target, buildFiles, tc.buildFiles)
   207  		}
   208  	}
   209  }
   210  
   211  func TestTargetExpressionToBuildFiles(t *testing.T) {
   212  	for _, buildFileName := range BuildFileNames {
   213  		runTestTargetExpressionToBuildFiles(t, buildFileName)
   214  	}
   215  }
   216  
   217  func runTestAppendCommands(t *testing.T, buildFileName string) {
   218  	tmp := setupTestTmpWorkspace(t, buildFileName)
   219  	defer os.RemoveAll(tmp)
   220  
   221  	for _, tc := range []targetExpressionToBuildFilesTestCase{
   222  		{tmp, ".:__pkg__", []string{"./" + buildFileName}},
   223  		{tmp, "a" + ":__pkg__", []string{"a/" + buildFileName}},
   224  		{"", "a" + ":__pkg__", []string{"a/" + buildFileName}},
   225  	} {
   226  		if tc.rootDir == "" {
   227  			cwd, err := os.Getwd()
   228  			if err != nil {
   229  				t.Fatal(err)
   230  			}
   231  			// buildozer should be able to find the WORKSPACE file in the current wd
   232  			if err := os.Chdir(tmp); err != nil {
   233  				t.Fatal(err)
   234  			}
   235  			defer os.Chdir(cwd)
   236  		}
   237  
   238  		commandsByFile := make(map[string][]commandsForTarget)
   239  		opts := NewOpts()
   240  		opts.RootDir = tc.rootDir
   241  		appendCommands(opts, commandsByFile, tc.buildFiles)
   242  		if len(commandsByFile) != 1 {
   243  			t.Errorf("Expect one target after appendCommands")
   244  		}
   245  		for _, value := range commandsByFile {
   246  			if value[0].target != tc.target {
   247  				t.Errorf("appendCommands for buildfile %s yielded target %s, expected %s", tc.buildFiles, value[0].target, tc.target)
   248  			}
   249  		}
   250  	}
   251  }
   252  
   253  func TestAppendCommands(t *testing.T) {
   254  	for _, buildFileName := range BuildFileNames {
   255  		runTestAppendCommands(t, buildFileName)
   256  	}
   257  }
   258  
   259  var dictListAddTests = []struct {
   260  	args      []string
   261  	buildFile string
   262  	expected  string
   263  }{
   264  	{[]string{
   265  		"attr", "key1", "value1",
   266  	},
   267  		`foo(
   268  		name = "foo",
   269  	)`,
   270  		`foo(
   271      name = "foo",
   272      attr = {"key1": ["value1"]},
   273  )`,
   274  	},
   275  	{[]string{
   276  		"attr", "key1", "value2",
   277  	},
   278  		`foo(
   279  		name = "foo",
   280  		attr = {"key1": ["value1"]},
   281  	)`,
   282  		`foo(
   283      name = "foo",
   284      attr = {"key1": [
   285          "value1",
   286          "value2",
   287      ]},
   288  )`,
   289  	},
   290  	{[]string{
   291  		"attr", "key1", "value1", "value2",
   292  	},
   293  		`foo(
   294  		name = "foo",
   295  	)`,
   296  		`foo(
   297      name = "foo",
   298      attr = {"key1": [
   299          "value1",
   300          "value2",
   301      ]},
   302  )`,
   303  	},
   304  	{[]string{
   305  		"attr", "key2", "value2",
   306  	},
   307  		`foo(
   308  		name = "foo",
   309  		attr = {"key1": ["value1"]},
   310  	)`,
   311  		`foo(
   312      name = "foo",
   313      attr = {
   314          "key1": ["value1"],
   315          "key2": ["value2"],
   316      },
   317  )`,
   318  	},
   319  	{[]string{
   320  		"attr", "key1", "value1",
   321  	},
   322  		`foo(
   323  		name = "foo",
   324  		attr = {"key1": ["value1"]},
   325  	)`,
   326  		`foo(
   327      name = "foo",
   328      attr = {"key1": ["value1"]},
   329  )`,
   330  	},
   331  }
   332  
   333  func TestCmdDictListAdd(t *testing.T) {
   334  	for i, tt := range dictListAddTests {
   335  		bld, err := build.Parse("BUILD", []byte(tt.buildFile))
   336  		if err != nil {
   337  			t.Error(err)
   338  			continue
   339  		}
   340  		rl := bld.Rules("foo")[0]
   341  		env := CmdEnvironment{
   342  			File: bld,
   343  			Rule: rl,
   344  			Args: tt.args,
   345  		}
   346  		bld, _ = cmdDictListAdd(NewOpts(), env)
   347  		got := strings.TrimSpace(string(build.Format(bld)))
   348  		if got != tt.expected {
   349  			t.Errorf("cmdDictListAdd(%d):\ngot:\n%s\nexpected:\n%s", i, got, tt.expected)
   350  		}
   351  	}
   352  }
   353  
   354  var substituteLoadsTests = []struct {
   355  	args      []string
   356  	buildFile string
   357  	expected  string
   358  }{
   359  	{[]string{
   360  		"^(.*)$", "${1}",
   361  	},
   362  		`load("//foo:foo.bzl", "foo")`,
   363  		`load("//foo:foo.bzl", "foo")`,
   364  	},
   365  	{[]string{
   366  		"^@rules_foo//foo:defs.bzl$", "//build/rules/foo:defs.bzl",
   367  	},
   368  		`load("@rules_bar//bar:defs.bzl", "bar")`,
   369  		`load("@rules_bar//bar:defs.bzl", "bar")`,
   370  	},
   371  	{[]string{
   372  		"^@rules_foo//foo:defs.bzl$", "//build/rules/foo:defs.bzl",
   373  	},
   374  		`load("@rules_foo//foo:defs.bzl", "foo", "foo2")
   375  load("@rules_bar//bar:defs.bzl", "bar")`,
   376  		`load("@rules_bar//bar:defs.bzl", "bar")
   377  load("//build/rules/foo:defs.bzl", "foo", "foo2")`,
   378  	},
   379  	{[]string{
   380  		":foo.bzl$", ":defs.bzl",
   381  	},
   382  		`load("//foo:foo.bzl", "foo")`,
   383  		`load("//foo:defs.bzl", "foo")`,
   384  	},
   385  	{[]string{
   386  		// Keep in sync with the example in `//buildozer:README.md`.
   387  		"^@([^/]*)//([^:].*)$", "//third_party/build_defs/${1}/${2}",
   388  	},
   389  		`load("@rules_foo//foo:defs.bzl", "foo", "foo2")
   390  load("@rules_bar//bar:defs.bzl", "bar")
   391  load("@rules_bar//:defs.bzl", legacy_bar = "bar")`,
   392  		`load("@rules_bar//:defs.bzl", legacy_bar = "bar")
   393  load("//third_party/build_defs/rules_bar/bar:defs.bzl", "bar")
   394  load("//third_party/build_defs/rules_foo/foo:defs.bzl", "foo", "foo2")`,
   395  	},
   396  }
   397  
   398  func TestCmdSubstituteLoad(t *testing.T) {
   399  	for i, tt := range substituteLoadsTests {
   400  		bld, err := build.Parse("BUILD", []byte(tt.buildFile))
   401  		if err != nil {
   402  			t.Error(err)
   403  			continue
   404  		}
   405  		env := CmdEnvironment{
   406  			File: bld,
   407  			Args: tt.args,
   408  		}
   409  		bld, _ = cmdSubstituteLoad(NewOpts(), env)
   410  		got := strings.TrimSpace(string(build.Format(bld)))
   411  		if got != tt.expected {
   412  			t.Errorf("cmdSubstituteLoad(%d):\ngot:\n%s\nexpected:\n%s", i, got, tt.expected)
   413  		}
   414  	}
   415  }
   416  
   417  func TestCmdDictAddSet_missingColon(t *testing.T) {
   418  	for _, tc := range []struct {
   419  		name string
   420  		fun  func(*Options, CmdEnvironment) (*build.File, error)
   421  	}{
   422  		{"dict_add", cmdDictAdd},
   423  		{"dict_set", cmdDictSet},
   424  	} {
   425  		t.Run(tc.name, func(t *testing.T) {
   426  			bld, err := build.Parse("BUILD", []byte("rule()"))
   427  			if err != nil {
   428  				t.Fatal(err)
   429  			}
   430  			env := CmdEnvironment{
   431  				File: bld,
   432  				Rule: bld.RuleAt(1),
   433  				Args: []string{"attr", "invalid"},
   434  			}
   435  			_, err = tc.fun(NewOpts(), env)
   436  			if err == nil {
   437  				t.Error("succeeded, want error")
   438  			}
   439  		})
   440  	}
   441  }
   442  
   443  func TestCmdSetSelect(t *testing.T) {
   444  	for i, tc := range []struct {
   445  		name      string
   446  		args      []string
   447  		buildFile string
   448  		expected  string
   449  	}{
   450  		{
   451  			name: "select_statement_doesn't_exist",
   452  			args: []string{
   453  				"args",                                   /* attr */
   454  				":use_ci_timeouts", "-test.timeout=123s", /* key, value */
   455  				":use_ci_timeouts", "-test.anotherFlag=flagValue", /* key, value */
   456  				"//conditions:default", "-test.timeout=789s", /* key, value */
   457  			},
   458  			buildFile: `foo(
   459  			name = "foo",
   460  )`,
   461  			expected: `foo(
   462      name = "foo",
   463      args = select({
   464          ":use_ci_timeouts": [
   465              "-test.timeout=123s",
   466              "-test.anotherFlag=flagValue",
   467          ],
   468          "//conditions:default": ["-test.timeout=789s"],
   469      }),
   470  )`},
   471  		{
   472  			name: "select_statement_exists",
   473  			args: []string{
   474  				"args",                                   /* attr */
   475  				":use_ci_timeouts", "-test.timeout=543s", /* key, value */
   476  				"//conditions:default", "-test.timeout=876s", /* key, value */
   477  			},
   478  			buildFile: `foo(
   479      name = "foo",
   480      args = select({
   481          ":use_ci_timeouts": [
   482              "-test.timeout=123s",
   483              "-test.anotherFlag=flagValue",
   484          ],
   485          "//conditions:default": ["-test.timeout=789s"],
   486      }),
   487  )`,
   488  			expected: `foo(
   489      name = "foo",
   490      args = select({
   491          ":use_ci_timeouts": ["-test.timeout=543s"],
   492          "//conditions:default": ["-test.timeout=876s"],
   493      }),
   494  )`},
   495  		{
   496  			name: "attr_exists_but_not_select",
   497  			args: []string{
   498  				"args",                                   /* attr */
   499  				":use_ci_timeouts", "-test.timeout=543s", /* key, value */
   500  				"//conditions:default", "-test.timeout=876s", /* key, value */
   501  			},
   502  			buildFile: `foo(
   503      name = "foo",
   504      args = ["-test.timeout=123s"],
   505  )`,
   506  			expected: `foo(
   507      name = "foo",
   508      args = select({
   509          ":use_ci_timeouts": ["-test.timeout=543s"],
   510          "//conditions:default": ["-test.timeout=876s"],
   511      }),
   512  )`},
   513  	} {
   514  		t.Run(tc.name, func(t *testing.T) {
   515  			bld, err := build.Parse("BUILD", []byte(tc.buildFile))
   516  			if err != nil {
   517  				t.Error(err)
   518  			}
   519  			rl := bld.Rules("foo")[0]
   520  			env := CmdEnvironment{
   521  				File: bld,
   522  				Rule: rl,
   523  				Args: tc.args,
   524  			}
   525  			bld, _ = cmdSetSelect(NewOpts(), env)
   526  			got := strings.TrimSpace(string(build.Format(bld)))
   527  			if got != tc.expected {
   528  				t.Errorf("cmdSetSelect(%d):\ngot:\n%s\nexpected:\n%s", i, got, tc.expected)
   529  			}
   530  		})
   531  	}
   532  }
   533  

View as plain text