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

     1  /* Copyright 2017 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  	"go/build/constraint"
    20  	"os"
    21  	"path/filepath"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  )
    27  
    28  var (
    29  	fileInfoCmpOption = cmp.AllowUnexported(
    30  		fileInfo{},
    31  		fileEmbed{},
    32  		buildTags{},
    33  		cgoTagsAndOpts{},
    34  	)
    35  )
    36  
    37  func TestGoFileInfo(t *testing.T) {
    38  	for _, tc := range []struct {
    39  		desc, name, source string
    40  		want               fileInfo
    41  	}{
    42  		{
    43  			"empty file",
    44  			"foo.go",
    45  			"package foo\n",
    46  			fileInfo{
    47  				packageName: "foo",
    48  			},
    49  		},
    50  		{
    51  			"xtest file",
    52  			"foo_test.go",
    53  			"package foo_test\n",
    54  			fileInfo{
    55  				packageName: "foo",
    56  				isTest:      true,
    57  			},
    58  		},
    59  		{
    60  			"xtest suffix on non-test",
    61  			"foo_xtest.go",
    62  			"package foo_test\n",
    63  			fileInfo{
    64  				packageName: "foo_test",
    65  				isTest:      false,
    66  			},
    67  		},
    68  		{
    69  			"single import",
    70  			"foo.go",
    71  			`package foo
    72  
    73  import "github.com/foo/bar"
    74  `,
    75  			fileInfo{
    76  				packageName: "foo",
    77  				imports:     []string{"github.com/foo/bar"},
    78  			},
    79  		},
    80  		{
    81  			"multiple imports",
    82  			"foo.go",
    83  			`package foo
    84  
    85  import (
    86  	"github.com/foo/bar"
    87  	x "github.com/local/project/y"
    88  )
    89  `,
    90  			fileInfo{
    91  				packageName: "foo",
    92  				imports:     []string{"github.com/foo/bar", "github.com/local/project/y"},
    93  			},
    94  		},
    95  		{
    96  			"standard imports included",
    97  			"foo.go",
    98  			`package foo
    99  
   100  import "fmt"
   101  `,
   102  			fileInfo{
   103  				packageName: "foo",
   104  				imports:     []string{"fmt"},
   105  			},
   106  		},
   107  		{
   108  			"cgo",
   109  			"foo.go",
   110  			`package foo
   111  
   112  import "C"
   113  `,
   114  			fileInfo{
   115  				packageName: "foo",
   116  				isCgo:       true,
   117  			},
   118  		},
   119  		{
   120  			"build tags",
   121  			"foo.go",
   122  			`// +build linux darwin
   123  
   124  // +build !ignore
   125  
   126  package foo
   127  `,
   128  			fileInfo{
   129  				packageName: "foo",
   130  				tags: &buildTags{
   131  					expr:    mustParseBuildTag(t, "(linux || darwin) && !ignore"),
   132  					rawTags: []string{"linux", "darwin", "ignore"},
   133  				},
   134  			},
   135  		},
   136  		{
   137  			"build tags without blank line",
   138  			"route.go",
   139  			`// Copyright 2017
   140  
   141  // +build darwin dragonfly freebsd netbsd openbsd
   142  
   143  // Package route provides basic functions for the manipulation of
   144  // packet routing facilities on BSD variants.
   145  package route
   146  `,
   147  			fileInfo{
   148  				packageName: "route",
   149  				tags: &buildTags{
   150  					expr:    mustParseBuildTag(t, "darwin || dragonfly || freebsd || netbsd || openbsd"),
   151  					rawTags: []string{"darwin", "dragonfly", "freebsd", "netbsd", "openbsd"},
   152  				},
   153  			},
   154  		},
   155  		{
   156  			"embed",
   157  			"embed.go",
   158  			`package foo
   159  
   160  import _ "embed"
   161  
   162  //go:embed embed.go
   163  var src string
   164  `,
   165  			fileInfo{
   166  				packageName: "foo",
   167  				imports:     []string{"embed"},
   168  				embeds:      []fileEmbed{{path: "embed.go"}},
   169  			},
   170  		},
   171  	} {
   172  		t.Run(tc.desc, func(t *testing.T) {
   173  			dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestGoFileInfo")
   174  			if err != nil {
   175  				t.Fatal(err)
   176  			}
   177  			defer os.RemoveAll(dir)
   178  			path := filepath.Join(dir, tc.name)
   179  			if err := os.WriteFile(path, []byte(tc.source), 0o600); err != nil {
   180  				t.Fatal(err)
   181  			}
   182  
   183  			got := goFileInfo(path, "")
   184  			// Clear fields we don't care about for testing.
   185  			got = fileInfo{
   186  				packageName: got.packageName,
   187  				isTest:      got.isTest,
   188  				imports:     got.imports,
   189  				embeds:      got.embeds,
   190  				isCgo:       got.isCgo,
   191  				tags:        got.tags,
   192  			}
   193  			for i := range got.embeds {
   194  				got.embeds[i] = fileEmbed{path: got.embeds[i].path}
   195  			}
   196  
   197  			if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
   198  				t.Errorf("(-want, +got): %s", diff)
   199  			}
   200  
   201  		})
   202  	}
   203  }
   204  
   205  func TestGoFileInfoFailure(t *testing.T) {
   206  	dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestGoFileInfoFailure")
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	defer os.RemoveAll(dir)
   211  	name := "foo_linux_amd64.go"
   212  	path := filepath.Join(dir, name)
   213  	if err := os.WriteFile(path, []byte("pakcage foo"), 0o600); err != nil {
   214  		t.Fatal(err)
   215  	}
   216  
   217  	got := goFileInfo(path, "")
   218  	want := fileInfo{
   219  		path:   path,
   220  		name:   name,
   221  		ext:    goExt,
   222  		goos:   "linux",
   223  		goarch: "amd64",
   224  	}
   225  	if diff := cmp.Diff(want, got, fileInfoCmpOption); diff != "" {
   226  		t.Errorf("(-want, +got): %s", diff)
   227  	}
   228  
   229  }
   230  
   231  func TestCgo(t *testing.T) {
   232  	for _, tc := range []struct {
   233  		desc, source string
   234  		want         fileInfo
   235  	}{
   236  		{
   237  			"not cgo",
   238  			"package foo\n",
   239  			fileInfo{isCgo: false},
   240  		},
   241  		{
   242  			"empty cgo",
   243  			`package foo
   244  
   245  import "C"
   246  `,
   247  			fileInfo{isCgo: true},
   248  		},
   249  		{
   250  			"simple flags",
   251  			`package foo
   252  
   253  /*
   254  #cgo CFLAGS: -O0
   255  	#cgo CPPFLAGS: -O1
   256  #cgo   CXXFLAGS:   -O2
   257  #cgo LDFLAGS: -O3 -O4
   258  */
   259  import "C"
   260  `,
   261  			fileInfo{
   262  				isCgo: true,
   263  				cppopts: []*cgoTagsAndOpts{
   264  					{opts: "-O1"},
   265  				},
   266  				copts: []*cgoTagsAndOpts{
   267  					{opts: "-O0"},
   268  				},
   269  				cxxopts: []*cgoTagsAndOpts{
   270  					{opts: "-O2"},
   271  				},
   272  				clinkopts: []*cgoTagsAndOpts{
   273  					{opts: strings.Join([]string{"-O3", "-O4"}, optSeparator)},
   274  				},
   275  			},
   276  		},
   277  		{
   278  			"cflags with conditions",
   279  			`package foo
   280  
   281  /*
   282  #cgo foo bar,!baz CFLAGS: -O0
   283  */
   284  import "C"
   285  `,
   286  			fileInfo{
   287  				isCgo: true,
   288  				copts: []*cgoTagsAndOpts{
   289  					{
   290  						buildTags: &buildTags{
   291  							expr:    mustParseBuildTag(t, "foo || (bar && !baz)"),
   292  							rawTags: []string{"foo", "bar", "baz"},
   293  						},
   294  						opts: "-O0",
   295  					},
   296  				},
   297  			},
   298  		},
   299  		{
   300  			"slashslash comments",
   301  			`package foo
   302  
   303  // #cgo CFLAGS: -O0
   304  // #cgo CFLAGS: -O1
   305  import "C"
   306  `,
   307  			fileInfo{
   308  				isCgo: true,
   309  				copts: []*cgoTagsAndOpts{
   310  					{opts: "-O0"},
   311  					{opts: "-O1"},
   312  				},
   313  			},
   314  		},
   315  		{
   316  			"comment above single import group",
   317  			`package foo
   318  
   319  /*
   320  #cgo CFLAGS: -O0
   321  */
   322  import ("C")
   323  `,
   324  			fileInfo{
   325  				isCgo: true,
   326  				copts: []*cgoTagsAndOpts{
   327  					{opts: "-O0"},
   328  				},
   329  			},
   330  		},
   331  	} {
   332  		t.Run(tc.desc, func(t *testing.T) {
   333  			dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCgo")
   334  			if err != nil {
   335  				t.Fatal(err)
   336  			}
   337  			defer os.RemoveAll(dir)
   338  			name := "TestCgo.go"
   339  			path := filepath.Join(dir, name)
   340  			if err := os.WriteFile(path, []byte(tc.source), 0o600); err != nil {
   341  				t.Fatal(err)
   342  			}
   343  
   344  			got := goFileInfo(path, "")
   345  
   346  			// Clear fields we don't care about for testing.
   347  			got = fileInfo{
   348  				isCgo:     got.isCgo,
   349  				copts:     got.copts,
   350  				cppopts:   got.cppopts,
   351  				cxxopts:   got.cxxopts,
   352  				clinkopts: got.clinkopts,
   353  			}
   354  
   355  			if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
   356  				t.Errorf("(-want, +got): %s", diff)
   357  			}
   358  
   359  		})
   360  	}
   361  }
   362  
   363  // Copied from go/build build_test.go
   364  var (
   365  	expandSrcDirPath = filepath.Join(string(filepath.Separator)+"projects", "src", "add")
   366  )
   367  
   368  // Copied from go/build build_test.go
   369  var expandSrcDirTests = []struct {
   370  	input, expected string
   371  }{
   372  	{"-L ${SRCDIR}/libs -ladd", "-L /projects/src/add/libs -ladd"},
   373  	{"${SRCDIR}/add_linux_386.a -pthread -lstdc++", "/projects/src/add/add_linux_386.a -pthread -lstdc++"},
   374  	{"Nothing to expand here!", "Nothing to expand here!"},
   375  	{"$", "$"},
   376  	{"$$", "$$"},
   377  	{"${", "${"},
   378  	{"$}", "$}"},
   379  	{"$FOO ${BAR}", "$FOO ${BAR}"},
   380  	{"Find me the $SRCDIRECTORY.", "Find me the $SRCDIRECTORY."},
   381  	{"$SRCDIR is missing braces", "$SRCDIR is missing braces"},
   382  }
   383  
   384  // Copied from go/build build_test.go
   385  func TestExpandSrcDir(t *testing.T) {
   386  	for _, test := range expandSrcDirTests {
   387  		output, _ := expandSrcDir(test.input, expandSrcDirPath)
   388  		if output != test.expected {
   389  			t.Errorf("%q expands to %q with SRCDIR=%q when %q is expected", test.input, output, expandSrcDirPath, test.expected)
   390  		} else {
   391  			t.Logf("%q expands to %q with SRCDIR=%q", test.input, output, expandSrcDirPath)
   392  		}
   393  	}
   394  }
   395  
   396  var (
   397  	goPackageCmpOption = cmp.AllowUnexported(
   398  		goPackage{},
   399  		goTarget{},
   400  		protoTarget{},
   401  		platformStringsBuilder{},
   402  		platformStringInfo{},
   403  	)
   404  )
   405  
   406  func TestExpandSrcDirRepoRelative(t *testing.T) {
   407  	repo, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "repo")
   408  	if err != nil {
   409  		t.Fatal(err)
   410  	}
   411  	sub := filepath.Join(repo, "sub")
   412  	if err := os.Mkdir(sub, 0o755); err != nil {
   413  		t.Fatal(err)
   414  	}
   415  	goFile := filepath.Join(sub, "sub.go")
   416  	content := []byte(`package sub
   417  
   418  /*
   419  #cgo CFLAGS: -I${SRCDIR}/..
   420  */
   421  import "C"
   422  `)
   423  	if err := os.WriteFile(goFile, content, 0o644); err != nil {
   424  		t.Fatal(err)
   425  	}
   426  	c, _, _ := testConfig(
   427  		t,
   428  		"-repo_root="+repo,
   429  		"-go_prefix=example.com/repo")
   430  	fi := goFileInfo(filepath.Join(sub, "sub.go"), "sub")
   431  	pkgs, _ := buildPackages(c, sub, "sub", false, nil, []fileInfo{fi})
   432  	got, ok := pkgs["sub"]
   433  	if !ok {
   434  		t.Fatal("did not build package 'sub'")
   435  	}
   436  	want := &goPackage{
   437  		name:    "sub",
   438  		dir:     sub,
   439  		rel:     "sub",
   440  		library: goTarget{cgo: true},
   441  	}
   442  	want.library.sources.addGenericString("sub.go")
   443  	want.library.copts.addGenericString("-Isub/..")
   444  	if diff := cmp.Diff(want, got, goPackageCmpOption); diff != "" {
   445  		t.Errorf("(-want, +got): %s", diff)
   446  	}
   447  
   448  }
   449  
   450  // Copied from go/build build_test.go
   451  func TestShellSafety(t *testing.T) {
   452  	tests := []struct {
   453  		input, srcdir, expected string
   454  		result                  bool
   455  	}{
   456  		{"-I${SRCDIR}/../include", "/projects/src/issue 11868", "-I/projects/src/issue 11868/../include", true},
   457  		{"-I${SRCDIR}", "wtf$@%", "-Iwtf$@%", true},
   458  		{"-X${SRCDIR}/1,${SRCDIR}/2", "/projects/src/issue 11868", "-X/projects/src/issue 11868/1,/projects/src/issue 11868/2", true},
   459  		{"-I/tmp -I/tmp", "/tmp2", "-I/tmp -I/tmp", false},
   460  		{"-I/tmp", "/tmp/[0]", "-I/tmp", true},
   461  		{"-I${SRCDIR}/dir", "/tmp/[0]", "-I/tmp/[0]/dir", false},
   462  	}
   463  	for _, test := range tests {
   464  		output, ok := expandSrcDir(test.input, test.srcdir)
   465  		if ok != test.result {
   466  			t.Errorf("Expected %t while %q expands to %q with SRCDIR=%q; got %t", test.result, test.input, output, test.srcdir, ok)
   467  		}
   468  		if output != test.expected {
   469  			t.Errorf("Expected %q while %q expands with SRCDIR=%q; got %q", test.expected, test.input, test.srcdir, output)
   470  		}
   471  	}
   472  }
   473  
   474  func mustParseBuildTag(t *testing.T, in string) constraint.Expr {
   475  	x, err := constraint.Parse("//go:build " + in)
   476  	if err != nil {
   477  		t.Fatalf("%s: %s", in, err)
   478  	}
   479  
   480  	return x
   481  }