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

     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 golang
    17  
    18  import (
    19  	"os"
    20  	"path"
    21  	"path/filepath"
    22  	"runtime"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/bazelbuild/bazel-gazelle/config"
    27  	"github.com/bazelbuild/bazel-gazelle/language"
    28  	"github.com/bazelbuild/bazel-gazelle/language/proto"
    29  	"github.com/bazelbuild/bazel-gazelle/merger"
    30  	"github.com/bazelbuild/bazel-gazelle/rule"
    31  	"github.com/bazelbuild/bazel-gazelle/walk"
    32  	bzl "github.com/bazelbuild/buildtools/build"
    33  	"github.com/bazelbuild/rules_go/go/tools/bazel"
    34  	"github.com/google/go-cmp/cmp"
    35  )
    36  
    37  func TestGenerateRules(t *testing.T) {
    38  	testdataDir := "testdata"
    39  	if runtime.GOOS == "windows" {
    40  		var err error
    41  		testdataDir, err = bazel.NewTmpDir("testdata")
    42  		if err != nil {
    43  			t.Fatal(err)
    44  		}
    45  		files, _ := bazel.ListRunfiles()
    46  		parent := "language/go/testdata"
    47  		for _, rf := range files {
    48  			rel, err := filepath.Rel(parent, rf.ShortPath)
    49  			if err != nil {
    50  				continue
    51  			}
    52  			if strings.HasPrefix(rel, "..") {
    53  				// make sure we're not moving around file that we're not inrerested in
    54  				continue
    55  			}
    56  			newPath := filepath.FromSlash(path.Join(testdataDir, rel))
    57  			if err := os.MkdirAll(filepath.FromSlash(filepath.Dir(newPath)), os.ModePerm); err != nil {
    58  				t.Fatal(err)
    59  			}
    60  			if err := os.Link(filepath.FromSlash(rf.Path), newPath); err != nil {
    61  				t.Fatal(err)
    62  			}
    63  		}
    64  	}
    65  
    66  	c, langs, cexts := testConfig(
    67  		t,
    68  		"-build_file_name=BUILD.old",
    69  		"-go_prefix=example.com/repo",
    70  		"-repo_root="+testdataDir)
    71  
    72  	// runfiles are symbolic links, which we need Walk to follow.
    73  	content := []byte(`
    74  # gazelle:follow **
    75  `)
    76  	f, err := rule.LoadData(filepath.FromSlash("BUILD.config"), "config", content)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	for _, cext := range cexts {
    81  		cext.Configure(c, "", f)
    82  	}
    83  
    84  	var loads []rule.LoadInfo
    85  	for _, lang := range langs {
    86  		loads = append(loads, lang.(language.ModuleAwareLanguage).ApparentLoads(func(string) string { return "" })...)
    87  	}
    88  	var testsFound int
    89  	walk.Walk(c, cexts, []string{testdataDir}, walk.VisitAllUpdateSubdirsMode, func(dir, rel string, c *config.Config, update bool, oldFile *rule.File, subdirs, regularFiles, genFiles []string) {
    90  		t.Run(rel, func(t *testing.T) {
    91  			var empty, gen []*rule.Rule
    92  			for _, lang := range langs {
    93  				res := lang.GenerateRules(language.GenerateArgs{
    94  					Config:       c,
    95  					Dir:          dir,
    96  					Rel:          rel,
    97  					File:         oldFile,
    98  					Subdirs:      subdirs,
    99  					RegularFiles: regularFiles,
   100  					GenFiles:     genFiles,
   101  					OtherEmpty:   empty,
   102  					OtherGen:     gen,
   103  				})
   104  				empty = append(empty, res.Empty...)
   105  				gen = append(gen, res.Gen...)
   106  			}
   107  			isTest := false
   108  			for _, name := range regularFiles {
   109  				if name == "BUILD.want" {
   110  					isTest = true
   111  					break
   112  				}
   113  			}
   114  			if !isTest {
   115  				// GenerateRules may have side effects, so we need to run it, even if
   116  				// there's no test.
   117  				return
   118  			}
   119  			testsFound += 1
   120  			f := rule.EmptyFile("test", "")
   121  			for _, r := range gen {
   122  				r.Insert(f)
   123  			}
   124  			convertImportsAttrs(f)
   125  			merger.FixLoads(f, loads)
   126  			f.Sync()
   127  			got := string(bzl.Format(f.File))
   128  			wantPath := filepath.Join(dir, "BUILD.want")
   129  			wantBytes, err := os.ReadFile(wantPath)
   130  			if err != nil {
   131  				t.Fatalf("error reading %s: %v", wantPath, err)
   132  			}
   133  			want := string(wantBytes)
   134  			want = strings.ReplaceAll(want, "\r\n", "\n")
   135  			if diff := cmp.Diff(want, got); diff != "" {
   136  				t.Errorf("(-want, +got): %s", diff)
   137  			}
   138  		})
   139  	})
   140  	// Avoid spurious success if we fail to find any tests.
   141  	if testsFound == 0 {
   142  		t.Error("No rule generation tests were found")
   143  	}
   144  }
   145  
   146  func TestGenerateRulesEmpty(t *testing.T) {
   147  	c, langs, _ := testConfig(t, "-go_prefix=example.com/repo")
   148  	goLang := langs[1].(*goLang)
   149  	res := goLang.GenerateRules(language.GenerateArgs{
   150  		Config: c,
   151  		Dir:    "./foo",
   152  		Rel:    "foo",
   153  	})
   154  	if len(res.Gen) > 0 {
   155  		t.Errorf("got %d generated rules; want 0", len(res.Gen))
   156  	}
   157  	f := rule.EmptyFile("test", "")
   158  	for _, r := range res.Empty {
   159  		r.Insert(f)
   160  	}
   161  	f.Sync()
   162  	got := strings.TrimSpace(string(bzl.Format(f.File)))
   163  	want := strings.TrimSpace(`
   164  filegroup(name = "go_default_library_protos")
   165  
   166  go_proto_library(name = "foo_go_proto")
   167  
   168  go_library(name = "foo")
   169  
   170  go_binary(name = "foo")
   171  
   172  go_test(name = "foo_test")
   173  `)
   174  	if got != want {
   175  		t.Errorf("got:\n%s\nwant:\n%s", got, want)
   176  	}
   177  }
   178  
   179  func TestGenerateRulesEmptyLegacyProto(t *testing.T) {
   180  	c, langs, _ := testConfig(t, "-proto=legacy")
   181  	goLang := langs[len(langs)-1].(*goLang)
   182  	res := goLang.GenerateRules(language.GenerateArgs{
   183  		Config: c,
   184  		Dir:    "./foo",
   185  		Rel:    "foo",
   186  	})
   187  	for _, e := range res.Empty {
   188  		if kind := e.Kind(); kind == "proto_library" || kind == "go_proto_library" || kind == "go_grpc_library" {
   189  			t.Errorf("deleted rule %s ; should not delete in legacy proto mode", kind)
   190  		}
   191  	}
   192  }
   193  
   194  func TestGenerateRulesEmptyPackageProto(t *testing.T) {
   195  	c, langs, _ := testConfig(t, "-proto=package", "-go_prefix=example.com/repo")
   196  	oldContent := []byte(`
   197  proto_library(
   198      name = "dead_proto",
   199      srcs = ["dead.proto"],
   200  )
   201  `)
   202  	old, err := rule.LoadData("BUILD.bazel", "", oldContent)
   203  	if err != nil {
   204  		t.Fatal(err)
   205  	}
   206  	var empty []*rule.Rule
   207  	for _, lang := range langs {
   208  		res := lang.GenerateRules(language.GenerateArgs{
   209  			Config:     c,
   210  			Dir:        "./foo",
   211  			Rel:        "foo",
   212  			File:       old,
   213  			OtherEmpty: empty,
   214  		})
   215  		empty = append(empty, res.Empty...)
   216  	}
   217  	f := rule.EmptyFile("test", "")
   218  	for _, r := range empty {
   219  		r.Insert(f)
   220  	}
   221  	f.Sync()
   222  	got := strings.TrimSpace(string(bzl.Format(f.File)))
   223  	want := strings.TrimSpace(`
   224  proto_library(name = "dead_proto")
   225  
   226  go_proto_library(name = "dead_go_proto")
   227  
   228  filegroup(name = "go_default_library_protos")
   229  
   230  go_proto_library(name = "foo_go_proto")
   231  
   232  go_library(name = "foo")
   233  
   234  go_binary(name = "foo")
   235  
   236  go_test(name = "foo_test")
   237  `)
   238  	if got != want {
   239  		t.Errorf("got:\n%s\nwant:\n%s", got, want)
   240  	}
   241  }
   242  
   243  func TestGenerateRulesPrebuiltGoProtoRules(t *testing.T) {
   244  	for _, protoFlag := range []string{
   245  		"-proto=default",
   246  		"-proto=package",
   247  	} {
   248  		t.Run("with flag: "+protoFlag, func(t *testing.T) {
   249  			c, langs, _ := testConfig(t, protoFlag)
   250  			goLang := langs[len(langs)-1].(*goLang)
   251  
   252  			res := goLang.GenerateRules(language.GenerateArgs{
   253  				Config:   c,
   254  				Dir:      "./foo",
   255  				Rel:      "foo",
   256  				OtherGen: prebuiltProtoRules(),
   257  			})
   258  
   259  			if len(res.Gen) != 0 {
   260  				t.Errorf("got %d generated rules; want 0", len(res.Gen))
   261  			}
   262  			f := rule.EmptyFile("test", "")
   263  			for _, r := range res.Gen {
   264  				r.Insert(f)
   265  			}
   266  			f.Sync()
   267  			got := strings.TrimSpace(string(bzl.Format(f.File)))
   268  			want := strings.TrimSpace(`
   269  		`)
   270  			if got != want {
   271  				t.Errorf("got:\n%s\nwant:\n%s", got, want)
   272  			}
   273  		})
   274  	}
   275  }
   276  
   277  // Test generated files that have been consumed by other rules should not be
   278  // added to the go_default_library rule
   279  func TestConsumedGenFiles(t *testing.T) {
   280  	args := language.GenerateArgs{
   281  		RegularFiles: []string{"regular.go"},
   282  		GenFiles:     []string{"mocks.go"},
   283  		Config: &config.Config{
   284  			Exts: make(map[string]interface{}),
   285  		},
   286  	}
   287  	otherRule := rule.NewRule("go_library", "go_mock_library")
   288  	otherRule.SetAttr("srcs", []string{"mocks.go"})
   289  	args.OtherGen = append(args.OtherGen, otherRule)
   290  
   291  	gl := goLang{
   292  		goPkgRels: make(map[string]bool),
   293  	}
   294  	gl.Configure(args.Config, "", nil)
   295  	res := gl.GenerateRules(args)
   296  	got := res.Gen[0].AttrStrings("srcs")
   297  	want := []string{"regular.go"}
   298  	if len(got) != len(want) || got[0] != want[0] {
   299  		t.Errorf("got:\n%s\nwant:\n%s", got, want)
   300  	}
   301  }
   302  
   303  // Test visibility attribute is only set if no default visibility is provided
   304  // by the file or other rules.
   305  func TestShouldSetVisibility(t *testing.T) {
   306  	if !shouldSetVisibility(language.GenerateArgs{}) {
   307  		t.Error("got 'False' for shouldSetVisibility with default args; expected 'True'")
   308  	}
   309  
   310  	if !shouldSetVisibility(language.GenerateArgs{
   311  		File: rule.EmptyFile("path", "pkg"),
   312  	}) {
   313  		t.Error("got 'False' for shouldSetVisibility with empty file; expected 'True'")
   314  	}
   315  
   316  	fileWithDefaultVisibile, _ := rule.LoadData("path", "pkg", []byte(`package(default_visibility = "//src:__subpackages__")`))
   317  	if shouldSetVisibility(language.GenerateArgs{
   318  		File: fileWithDefaultVisibile,
   319  	}) {
   320  		t.Error("got 'True' for shouldSetVisibility with file with default visibility; expected 'False'")
   321  	}
   322  
   323  	defaultVisibilityRule := rule.NewRule("package", "")
   324  	defaultVisibilityRule.SetAttr("default_visibility", []string{"//src:__subpackages__"})
   325  	if shouldSetVisibility(language.GenerateArgs{
   326  		OtherGen: []*rule.Rule{defaultVisibilityRule},
   327  	}) {
   328  		t.Error("got 'True' for shouldSetVisibility with rule defining a default visibility; expected 'False'")
   329  	}
   330  }
   331  
   332  func prebuiltProtoRules() []*rule.Rule {
   333  	protoRule := rule.NewRule("proto_library", "foo_proto")
   334  	protoRule.SetAttr("srcs", []string{"foo.proto"})
   335  	protoRule.SetAttr("visibility", []string{"//visibility:public"})
   336  	protoRule.SetPrivateAttr(proto.PackageKey,
   337  		proto.Package{
   338  			Name: "foo",
   339  			Files: map[string]proto.FileInfo{
   340  				"foo.proto": {},
   341  			},
   342  			Imports: map[string]bool{},
   343  			Options: map[string]string{},
   344  		},
   345  	)
   346  
   347  	goProtoRule := rule.NewRule("go_proto_library", "foo_go_proto")
   348  	goProtoRule.SetAttr("compilers", []string{"@io_bazel_rules_go//proto:go_proto"})
   349  	goProtoRule.SetAttr("importpath", "hello/world/foo")
   350  	goProtoRule.SetAttr("proto", ":foo_proto")
   351  	protoRule.SetAttr("visibility", []string{"//visibility:public"})
   352  
   353  	return []*rule.Rule{protoRule, goProtoRule}
   354  }
   355  
   356  // convertImportsAttrs copies private attributes to regular attributes, which
   357  // will later be written out to build files. This allows tests to check the
   358  // values of private attributes with simple string comparison.
   359  func convertImportsAttrs(f *rule.File) {
   360  	for _, r := range f.Rules {
   361  		v := r.PrivateAttr(config.GazelleImportsKey)
   362  		if v != nil {
   363  			r.SetAttr(config.GazelleImportsKey, v)
   364  		}
   365  	}
   366  }