github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/cmd/gazelle/fix_test.go (about)

     1  /* Copyright 2016 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 main
    17  
    18  import (
    19  	"flag"
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"runtime"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/bazelbuild/bazel-gazelle/testtools"
    28  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    29  )
    30  
    31  var goSdk = flag.String("go_sdk", "", "name of the go_sdk repository when invoked by Bazel")
    32  
    33  func TestMain(m *testing.M) {
    34  	status := 1
    35  	defer func() {
    36  		os.Exit(status)
    37  	}()
    38  
    39  	flag.Parse()
    40  
    41  	var err error
    42  	tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TMPDIR"), "gazelle_test")
    43  	if err != nil {
    44  		fmt.Fprintln(os.Stderr, err)
    45  		return
    46  	}
    47  	defer func() {
    48  		// Before deleting files in the temporary directory, add write permission
    49  		// to any files that don't have it. Files and directories in the module cache
    50  		// are read-only, and on Windows, the read-only bit prevents deletion and
    51  		// prevents Bazel from cleaning up the source tree.
    52  		_ = filepath.Walk(tmpDir, func(path string, info os.FileInfo, err error) error {
    53  			if err != nil {
    54  				return err
    55  			}
    56  			if mode := info.Mode(); mode&0o200 == 0 {
    57  				err = os.Chmod(path, mode|0o200)
    58  			}
    59  			return err
    60  		})
    61  		os.RemoveAll(tmpDir)
    62  	}()
    63  
    64  	if *goSdk != "" {
    65  		// This flag is only set when the test is run by Bazel. Figure out where
    66  		// the Go binary is and set GOROOT appropriately.
    67  		entries, err := bazel.ListRunfiles()
    68  		if err != nil {
    69  			fmt.Fprintln(os.Stderr, err)
    70  			return
    71  		}
    72  
    73  		var goToolPath string
    74  		ext := ""
    75  		if runtime.GOOS == "windows" {
    76  			ext = ".exe"
    77  		}
    78  		for _, entry := range entries {
    79  			if entry.Workspace == *goSdk && entry.ShortPath == "bin/go"+ext {
    80  				goToolPath = entry.Path
    81  				break
    82  			}
    83  		}
    84  		if goToolPath == "" {
    85  			fmt.Fprintln(os.Stderr, "could not locate go tool")
    86  			return
    87  		}
    88  		os.Setenv("GOROOT", filepath.Dir(filepath.Dir(goToolPath)))
    89  	}
    90  	os.Setenv("GOCACHE", filepath.Join(tmpDir, "gocache"))
    91  	os.Setenv("GOPATH", filepath.Join(tmpDir, "gopath"))
    92  
    93  	status = m.Run()
    94  }
    95  
    96  func defaultArgs(dir string) []string {
    97  	return []string{
    98  		"-repo_root", dir,
    99  		"-go_prefix", "example.com/repo",
   100  		dir,
   101  	}
   102  }
   103  
   104  func TestCreateFile(t *testing.T) {
   105  	// Create a directory with a simple .go file.
   106  	tmpdir := os.Getenv("TEST_TMPDIR")
   107  	dir, err := os.MkdirTemp(tmpdir, "")
   108  	if err != nil {
   109  		t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err)
   110  	}
   111  	defer os.RemoveAll(dir)
   112  
   113  	goFile := filepath.Join(dir, "main.go")
   114  	if err = os.WriteFile(goFile, []byte("package main"), 0o600); err != nil {
   115  		t.Fatalf("error writing file %q: %v", goFile, err)
   116  	}
   117  
   118  	// Check that Gazelle creates a new file named "BUILD.bazel".
   119  	if err = run(dir, defaultArgs(dir)); err != nil {
   120  		t.Fatalf("run failed: %v", err)
   121  	}
   122  
   123  	buildFile := filepath.Join(dir, "BUILD.bazel")
   124  	if _, err = os.Stat(buildFile); err != nil {
   125  		t.Errorf("could not stat BUILD.bazel: %v", err)
   126  	}
   127  }
   128  
   129  func TestUpdateFile(t *testing.T) {
   130  	// Create a directory with a simple .go file and an empty BUILD file.
   131  	tmpdir := os.Getenv("TEST_TMPDIR")
   132  	dir, err := os.MkdirTemp(tmpdir, "")
   133  	if err != nil {
   134  		t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err)
   135  	}
   136  	defer os.RemoveAll(dir)
   137  
   138  	goFile := filepath.Join(dir, "main.go")
   139  	if err = os.WriteFile(goFile, []byte("package main"), 0o600); err != nil {
   140  		t.Fatalf("error writing file %q: %v", goFile, err)
   141  	}
   142  
   143  	buildFile := filepath.Join(dir, "BUILD")
   144  	if err = os.WriteFile(buildFile, nil, 0o600); err != nil {
   145  		t.Fatalf("error writing file %q: %v", buildFile, err)
   146  	}
   147  
   148  	// Check that Gazelle updates the BUILD file in place.
   149  	if err = run(dir, defaultArgs(dir)); err != nil {
   150  		t.Fatalf("run failed: %v", err)
   151  	}
   152  
   153  	if st, err := os.Stat(buildFile); err != nil {
   154  		t.Errorf("could not stat BUILD: %v", err)
   155  	} else if st.Size() == 0 {
   156  		t.Errorf("BUILD was not updated")
   157  	}
   158  
   159  	if _, err = os.Stat(filepath.Join(dir, "BUILD.bazel")); err == nil {
   160  		t.Errorf("BUILD.bazel should not exist")
   161  	}
   162  }
   163  
   164  func TestNoChanges(t *testing.T) {
   165  	// Create a directory with a BUILD file that doesn't need any changes.
   166  	tmpdir := os.Getenv("TEST_TMPDIR")
   167  	dir, err := os.MkdirTemp(tmpdir, "")
   168  	if err != nil {
   169  		t.Fatalf("os.MkdirTemp(%q, %q) failed with %v; want success", tmpdir, "", err)
   170  	}
   171  	defer os.RemoveAll(dir)
   172  
   173  	goFile := filepath.Join(dir, "main.go")
   174  	if err = os.WriteFile(goFile, []byte("package main\n\nfunc main() {}"), 0o600); err != nil {
   175  		t.Fatalf("error writing file %q: %v", goFile, err)
   176  	}
   177  
   178  	buildFile := filepath.Join(dir, "BUILD")
   179  	if err = os.WriteFile(buildFile, []byte(`load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   180  
   181  go_library(
   182      name = "go_default_library",
   183      srcs = ["main.go"],
   184      importpath = "example.com/repo",
   185      visibility = ["//visibility:private"],
   186  )
   187  
   188  go_binary(
   189      name = "hello",
   190      embed = [":go_default_library"],
   191      visibility = ["//visibility:public"],
   192  )
   193  `), 0o600); err != nil {
   194  		t.Fatalf("error writing file %q: %v", buildFile, err)
   195  	}
   196  	st, err := os.Stat(buildFile)
   197  	if err != nil {
   198  		t.Errorf("could not stat BUILD: %v", err)
   199  	}
   200  	modTime := st.ModTime()
   201  
   202  	// Ensure that Gazelle does not write to the BUILD file.
   203  	if err = run(dir, defaultArgs(dir)); err != nil {
   204  		t.Fatalf("run failed: %v", err)
   205  	}
   206  
   207  	if st, err := os.Stat(buildFile); err != nil {
   208  		t.Errorf("could not stat BUILD: %v", err)
   209  	} else if !modTime.Equal(st.ModTime()) {
   210  		t.Errorf("unexpected modificaiton to BUILD")
   211  	}
   212  }
   213  
   214  func TestFixReadWriteDir(t *testing.T) {
   215  	buildInFile := testtools.FileSpec{
   216  		Path: "in/BUILD.in",
   217  		Content: `
   218  go_binary(
   219      name = "hello",
   220      pure = "on",
   221  )
   222  `,
   223  	}
   224  	buildSrcFile := testtools.FileSpec{
   225  		Path:    "src/BUILD.bazel",
   226  		Content: `# src build file`,
   227  	}
   228  	oldFiles := []testtools.FileSpec{
   229  		buildInFile,
   230  		buildSrcFile,
   231  		{
   232  			Path: "src/hello.go",
   233  			Content: `
   234  package main
   235  
   236  func main() {}
   237  `,
   238  		},
   239  		{
   240  			Path:    "out/BUILD",
   241  			Content: `this should get replaced`,
   242  		},
   243  	}
   244  
   245  	for _, tc := range []struct {
   246  		desc string
   247  		args []string
   248  		want []testtools.FileSpec
   249  	}{
   250  		{
   251  			desc: "read",
   252  			args: []string{
   253  				"-repo_root={{dir}}/src",
   254  				"-experimental_read_build_files_dir={{dir}}/in",
   255  				"-build_file_name=BUILD.bazel,BUILD,BUILD.in",
   256  				"-go_prefix=example.com/repo",
   257  				"{{dir}}/src",
   258  			},
   259  			want: []testtools.FileSpec{
   260  				buildInFile,
   261  				{
   262  					Path: "src/BUILD.bazel",
   263  					Content: `
   264  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   265  
   266  go_binary(
   267      name = "hello",
   268      embed = [":repo_lib"],
   269      pure = "on",
   270      visibility = ["//visibility:public"],
   271  )
   272  
   273  go_library(
   274      name = "repo_lib",
   275      srcs = ["hello.go"],
   276      importpath = "example.com/repo",
   277      visibility = ["//visibility:private"],
   278  )
   279  `,
   280  				},
   281  			},
   282  		}, {
   283  			desc: "write",
   284  			args: []string{
   285  				"-repo_root={{dir}}/src",
   286  				"-experimental_write_build_files_dir={{dir}}/out",
   287  				"-build_file_name=BUILD.bazel,BUILD,BUILD.in",
   288  				"-go_prefix=example.com/repo",
   289  				"{{dir}}/src",
   290  			},
   291  			want: []testtools.FileSpec{
   292  				buildInFile,
   293  				buildSrcFile,
   294  				{
   295  					Path: "out/BUILD",
   296  					Content: `
   297  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   298  
   299  # src build file
   300  
   301  go_library(
   302      name = "repo_lib",
   303      srcs = ["hello.go"],
   304      importpath = "example.com/repo",
   305      visibility = ["//visibility:private"],
   306  )
   307  
   308  go_binary(
   309      name = "repo",
   310      embed = [":repo_lib"],
   311      visibility = ["//visibility:public"],
   312  )
   313  `,
   314  				},
   315  			},
   316  		}, {
   317  			desc: "read_and_write",
   318  			args: []string{
   319  				"-repo_root={{dir}}/src",
   320  				"-experimental_read_build_files_dir={{dir}}/in",
   321  				"-experimental_write_build_files_dir={{dir}}/out",
   322  				"-build_file_name=BUILD.bazel,BUILD,BUILD.in",
   323  				"-go_prefix=example.com/repo",
   324  				"{{dir}}/src",
   325  			},
   326  			want: []testtools.FileSpec{
   327  				buildInFile,
   328  				{
   329  					Path: "out/BUILD",
   330  					Content: `
   331  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   332  
   333  go_binary(
   334      name = "hello",
   335      embed = [":repo_lib"],
   336      pure = "on",
   337      visibility = ["//visibility:public"],
   338  )
   339  
   340  go_library(
   341      name = "repo_lib",
   342      srcs = ["hello.go"],
   343      importpath = "example.com/repo",
   344      visibility = ["//visibility:private"],
   345  )
   346  `,
   347  				},
   348  			},
   349  		},
   350  	} {
   351  		t.Run(tc.desc, func(t *testing.T) {
   352  			dir, cleanup := testtools.CreateFiles(t, oldFiles)
   353  			defer cleanup()
   354  			replacer := strings.NewReplacer("{{dir}}", dir, "/", string(os.PathSeparator))
   355  			for i := range tc.args {
   356  				if strings.HasPrefix(tc.args[i], "-go_prefix=") {
   357  					continue // don't put backslashes in prefix on windows
   358  				}
   359  				tc.args[i] = replacer.Replace(tc.args[i])
   360  			}
   361  			if err := run(dir, tc.args); err != nil {
   362  				t.Error(err)
   363  			}
   364  			testtools.CheckFiles(t, dir, tc.want)
   365  		})
   366  	}
   367  }
   368  
   369  func TestFix_LangFilter(t *testing.T) {
   370  	fixture := []testtools.FileSpec{
   371  		{
   372  			Path: "BUILD.bazel",
   373  			Content: `
   374  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   375  
   376  go_binary(
   377      name = "nofix",
   378      library = ":go_default_library",
   379      visibility = ["//visibility:public"],
   380  )
   381  
   382  go_library(
   383      name = "go_default_library",
   384      srcs = ["main.go"],
   385      importpath = "example.com/repo",
   386      visibility = ["//visibility:public"],
   387  )`,
   388  		},
   389  		{
   390  			Path:    "main.go",
   391  			Content: `package main`,
   392  		},
   393  	}
   394  
   395  	dir, cleanup := testtools.CreateFiles(t, fixture)
   396  	defer cleanup()
   397  
   398  	// Check that Gazelle does not update the BUILD file, due to lang filter.
   399  	if err := run(dir, []string{
   400  		"-repo_root", dir,
   401  		"-go_prefix", "example.com/repo",
   402  		"-lang=proto",
   403  		dir,
   404  	}); err != nil {
   405  		t.Fatalf("run failed: %v", err)
   406  	}
   407  
   408  	testtools.CheckFiles(t, dir, fixture)
   409  }
   410  
   411  func TestFix_MapKind_Argument(t *testing.T) {
   412  	for name, tc := range map[string]struct {
   413  		before []testtools.FileSpec
   414  		after  []testtools.FileSpec
   415  	}{
   416  		"same-name": {
   417  			before: []testtools.FileSpec{
   418  				{
   419  					Path: "BUILD.bazel",
   420  					Content: `
   421  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   422  
   423  # gazelle:map_kind go_binary go_binary //my:custom.bzl
   424  
   425  maybe(
   426      go_binary,
   427      name = "nofix",
   428      library = ":go_default_library",
   429      visibility = ["//visibility:public"],
   430  )
   431  
   432  go_library(
   433      name = "go_default_library",
   434      srcs = ["some.go"],
   435      importpath = "example.com/repo",
   436      visibility = ["//visibility:public"],
   437  )`,
   438  				},
   439  				{
   440  					Path:    "some.go",
   441  					Content: `package some`,
   442  				},
   443  			},
   444  			after: []testtools.FileSpec{
   445  				{
   446  					Path: "BUILD.bazel",
   447  					Content: `
   448  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   449  load("//my:custom.bzl", "go_binary")
   450  
   451  # gazelle:map_kind go_binary go_binary //my:custom.bzl
   452  
   453  maybe(
   454      go_binary,
   455      name = "nofix",
   456      library = ":go_default_library",
   457      visibility = ["//visibility:public"],
   458  )
   459  
   460  go_library(
   461      name = "go_default_library",
   462      srcs = ["some.go"],
   463      importpath = "example.com/repo",
   464      visibility = ["//visibility:public"],
   465  )`,
   466  				},
   467  			},
   468  		},
   469  		"different-name": {
   470  			before: []testtools.FileSpec{
   471  				{
   472  					Path: "BUILD.bazel",
   473  					Content: `
   474  load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
   475  
   476  # gazelle:map_kind go_binary custom_go_binary //my:custom.bzl
   477  
   478  maybe(
   479      go_binary,
   480      name = "nofix",
   481      library = ":go_default_library",
   482      visibility = ["//visibility:public"],
   483  )
   484  
   485  go_library(
   486      name = "go_default_library",
   487      srcs = ["some.go"],
   488      importpath = "example.com/repo",
   489      visibility = ["//visibility:public"],
   490  )`,
   491  				},
   492  				{
   493  					Path:    "some.go",
   494  					Content: `package some`,
   495  				},
   496  			},
   497  			after: []testtools.FileSpec{
   498  				{
   499  					Path: "BUILD.bazel",
   500  					Content: `
   501  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   502  load("//my:custom.bzl", "custom_go_binary")
   503  
   504  # gazelle:map_kind go_binary custom_go_binary //my:custom.bzl
   505  
   506  maybe(
   507      custom_go_binary,
   508      name = "nofix",
   509      library = ":go_default_library",
   510      visibility = ["//visibility:public"],
   511  )
   512  
   513  go_library(
   514      name = "go_default_library",
   515      srcs = ["some.go"],
   516      importpath = "example.com/repo",
   517      visibility = ["//visibility:public"],
   518  )`,
   519  				},
   520  			},
   521  		},
   522  		"non-loaded-symbol": {
   523  			before: []testtools.FileSpec{
   524  				{
   525  					Path: "BUILD.bazel",
   526  					Content: `
   527  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   528  
   529  custom_go_library = go_library
   530  
   531  # This will be ignored because it's not a directly loaded symbol when used:
   532  # gazelle:map_kind custom_go_library custom_go_library //my:custom.bzl
   533  
   534  maybe(
   535      custom_go_library,
   536      name = "nofix",
   537      library = ":go_default_library",
   538      visibility = ["//visibility:public"],
   539  )
   540  
   541  go_library(
   542      name = "go_default_library",
   543      srcs = ["some.go"],
   544      importpath = "example.com/repo",
   545      visibility = ["//visibility:public"],
   546  )
   547  `,
   548  				},
   549  				{
   550  					Path:    "some.go",
   551  					Content: `package some`,
   552  				},
   553  			},
   554  			after: []testtools.FileSpec{
   555  				{
   556  					Path: "BUILD.bazel",
   557  					Content: `
   558  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   559  
   560  custom_go_library = go_library
   561  
   562  # This will be ignored because it's not a directly loaded symbol when used:
   563  # gazelle:map_kind custom_go_library custom_go_library //my:custom.bzl
   564  
   565  maybe(
   566      custom_go_library,
   567      name = "nofix",
   568      library = ":go_default_library",
   569      visibility = ["//visibility:public"],
   570  )
   571  
   572  go_library(
   573      name = "go_default_library",
   574      srcs = ["some.go"],
   575      importpath = "example.com/repo",
   576      visibility = ["//visibility:public"],
   577  )
   578  `,
   579  				},
   580  			},
   581  		},
   582  		"not-arg-0": {
   583  			before: []testtools.FileSpec{
   584  				{
   585  					Path: "BUILD.bazel",
   586  					Content: `
   587  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   588  load("@other_rules//:def.bzl", "something_custom")
   589  
   590  # gazelle:map_kind something_custom something_custom //my:custom.bzl
   591  
   592  maybe(
   593      go_library,
   594      something_custom,
   595      name = "nofix",
   596      library = ":go_default_library",
   597      visibility = ["//visibility:public"],
   598  )
   599  
   600  go_library(
   601      name = "go_default_library",
   602      srcs = ["some.go"],
   603      importpath = "example.com/repo",
   604      visibility = ["//visibility:public"],
   605  )
   606  `,
   607  				},
   608  				{
   609  					Path:    "some.go",
   610  					Content: `package some`,
   611  				},
   612  			},
   613  			after: []testtools.FileSpec{
   614  				{
   615  					Path: "BUILD.bazel",
   616  					Content: `
   617  load("@io_bazel_rules_go//go:def.bzl", "go_library")
   618  load("@other_rules//:def.bzl", "something_custom")
   619  
   620  # gazelle:map_kind something_custom something_custom //my:custom.bzl
   621  
   622  maybe(
   623      go_library,
   624      something_custom,
   625      name = "nofix",
   626      library = ":go_default_library",
   627      visibility = ["//visibility:public"],
   628  )
   629  
   630  go_library(
   631      name = "go_default_library",
   632      srcs = ["some.go"],
   633      importpath = "example.com/repo",
   634      visibility = ["//visibility:public"],
   635  )
   636  `,
   637  				},
   638  			},
   639  		},
   640  	} {
   641  		t.Run(name, func(t *testing.T) {
   642  			dir, cleanup := testtools.CreateFiles(t, tc.before)
   643  			defer cleanup()
   644  
   645  			if err := run(dir, []string{
   646  				"-repo_root", dir,
   647  				"-go_prefix", "example.com/repo",
   648  				dir,
   649  			}); err != nil {
   650  				t.Fatalf("run failed: %v", err)
   651  			}
   652  
   653  			testtools.CheckFiles(t, dir, tc.after)
   654  		})
   655  	}
   656  }