github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/language/go/fileinfo_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  	"os"
    20  	"path/filepath"
    21  	"testing"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  )
    25  
    26  func TestOtherFileInfo(t *testing.T) {
    27  	dir := "."
    28  	for _, tc := range []struct {
    29  		desc, name, source string
    30  		wantTags           *buildTags
    31  	}{
    32  		{
    33  			"empty file",
    34  			"foo.c",
    35  			"",
    36  			nil,
    37  		},
    38  		{
    39  			"tags file",
    40  			"foo.c",
    41  			`// +build foo bar
    42  // +build baz,!ignore
    43  
    44  `,
    45  			&buildTags{
    46  				expr:    mustParseBuildTag(t, "(foo || bar) && (baz && !ignore)"),
    47  				rawTags: []string{"foo", "bar", "baz", "ignore"},
    48  			},
    49  		},
    50  	} {
    51  		t.Run(tc.desc, func(t *testing.T) {
    52  			if err := os.WriteFile(tc.name, []byte(tc.source), 0o600); err != nil {
    53  				t.Fatal(err)
    54  			}
    55  			defer os.Remove(tc.name)
    56  
    57  			got := otherFileInfo(filepath.Join(dir, tc.name))
    58  
    59  			// Only check that we can extract tags. Everything else is covered
    60  			// by other tests.
    61  			if diff := cmp.Diff(tc.wantTags, got.tags, fileInfoCmpOption); diff != "" {
    62  				t.Errorf("(-want, +got): %s", diff)
    63  			}
    64  		})
    65  	}
    66  }
    67  
    68  func TestFileNameInfo(t *testing.T) {
    69  	for _, tc := range []struct {
    70  		desc, name string
    71  		want       fileInfo
    72  	}{
    73  		{
    74  			"simple go file",
    75  			"simple.go",
    76  			fileInfo{
    77  				ext: goExt,
    78  			},
    79  		},
    80  		{
    81  			"simple go test",
    82  			"foo_test.go",
    83  			fileInfo{
    84  				ext:    goExt,
    85  				isTest: true,
    86  			},
    87  		},
    88  		{
    89  			"test source",
    90  			"test.go",
    91  			fileInfo{
    92  				ext:    goExt,
    93  				isTest: false,
    94  			},
    95  		},
    96  		{
    97  			"_test source",
    98  			"_test.go",
    99  			fileInfo{
   100  				ext: unknownExt,
   101  			},
   102  		},
   103  		{
   104  			"source with goos",
   105  			"foo_linux.go",
   106  			fileInfo{
   107  				ext:  goExt,
   108  				goos: "linux",
   109  			},
   110  		},
   111  		{
   112  			"source with goarch",
   113  			"foo_amd64.go",
   114  			fileInfo{
   115  				ext:    goExt,
   116  				goarch: "amd64",
   117  			},
   118  		},
   119  		{
   120  			"source with goos then goarch",
   121  			"foo_linux_amd64.go",
   122  			fileInfo{
   123  				ext:    goExt,
   124  				goos:   "linux",
   125  				goarch: "amd64",
   126  			},
   127  		},
   128  		{
   129  			"source with goarch then goos",
   130  			"foo_amd64_linux.go",
   131  			fileInfo{
   132  				ext:  goExt,
   133  				goos: "linux",
   134  			},
   135  		},
   136  		{
   137  			"test with goos and goarch",
   138  			"foo_linux_amd64_test.go",
   139  			fileInfo{
   140  				ext:    goExt,
   141  				goos:   "linux",
   142  				goarch: "amd64",
   143  				isTest: true,
   144  			},
   145  		},
   146  		{
   147  			"test then goos",
   148  			"foo_test_linux.go",
   149  			fileInfo{
   150  				ext:  goExt,
   151  				goos: "linux",
   152  			},
   153  		},
   154  		{
   155  			"goos source",
   156  			"linux.go",
   157  			fileInfo{
   158  				ext:  goExt,
   159  				goos: "",
   160  			},
   161  		},
   162  		{
   163  			"goarch source",
   164  			"amd64.go",
   165  			fileInfo{
   166  				ext:    goExt,
   167  				goarch: "",
   168  			},
   169  		},
   170  		{
   171  			"goos test",
   172  			"linux_test.go",
   173  			fileInfo{
   174  				ext:    goExt,
   175  				goos:   "",
   176  				isTest: true,
   177  			},
   178  		},
   179  		{
   180  			"c file",
   181  			"foo_test.cxx",
   182  			fileInfo{
   183  				ext:    cExt,
   184  				isTest: false,
   185  			},
   186  		},
   187  		{
   188  			"c os test file",
   189  			"foo_linux_test.c",
   190  			fileInfo{
   191  				ext:    cExt,
   192  				isTest: false,
   193  				goos:   "linux",
   194  			},
   195  		},
   196  		{
   197  			"h file",
   198  			"foo_linux.h",
   199  			fileInfo{
   200  				ext:  hExt,
   201  				goos: "linux",
   202  			},
   203  		},
   204  		{
   205  			"go asm file",
   206  			"foo_amd64.s",
   207  			fileInfo{
   208  				ext:    sExt,
   209  				goarch: "amd64",
   210  			},
   211  		},
   212  		{
   213  			"c asm file",
   214  			"foo.S",
   215  			fileInfo{
   216  				ext: csExt,
   217  			},
   218  		},
   219  		{
   220  			"unsupported file",
   221  			"foo.m",
   222  			fileInfo{
   223  				ext: cExt,
   224  			},
   225  		},
   226  		{
   227  			"ignored test file",
   228  			"foo_test.py",
   229  			fileInfo{
   230  				isTest: false,
   231  			},
   232  		},
   233  		{
   234  			"ignored xtest file",
   235  			"foo_xtest.py",
   236  			fileInfo{
   237  				isTest: false,
   238  			},
   239  		},
   240  		{
   241  			"ignored file",
   242  			"foo.txt",
   243  			fileInfo{
   244  				ext: unknownExt,
   245  			},
   246  		},
   247  		{
   248  			"hidden file",
   249  			".foo.go",
   250  			fileInfo{
   251  				ext: unknownExt,
   252  			},
   253  		},
   254  	} {
   255  		t.Run(tc.desc, func(t *testing.T) {
   256  			tc.want.name = tc.name
   257  			tc.want.path = filepath.Join("dir", tc.name)
   258  			got := fileNameInfo(tc.want.path)
   259  			if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
   260  				t.Errorf("(-want, +got): %s", diff)
   261  			}
   262  		})
   263  	}
   264  }
   265  
   266  func TestReadTags(t *testing.T) {
   267  	for _, tc := range []struct {
   268  		desc, source string
   269  		want         *buildTags
   270  	}{
   271  		{
   272  			"empty file",
   273  			"",
   274  			nil,
   275  		},
   276  		{
   277  			"single comment without blank line",
   278  			"// +build foo\npackage main",
   279  			nil,
   280  		},
   281  		{
   282  			"multiple comments without blank link",
   283  			`// +build foo
   284  
   285  // +build bar
   286  package main
   287  
   288  `,
   289  			&buildTags{
   290  				expr:    mustParseBuildTag(t, "foo"),
   291  				rawTags: []string{"foo"},
   292  			},
   293  		},
   294  		{
   295  			"single comment",
   296  			"// +build foo\n\n",
   297  			&buildTags{
   298  				expr:    mustParseBuildTag(t, "foo"),
   299  				rawTags: []string{"foo"},
   300  			},
   301  		},
   302  		{
   303  			"multiple comments",
   304  			`// +build foo
   305  // +build bar
   306  
   307  package main`,
   308  			&buildTags{
   309  				expr:    mustParseBuildTag(t, "foo && bar"),
   310  				rawTags: []string{"foo", "bar"},
   311  			},
   312  		},
   313  		{
   314  			"multiple comments with blank",
   315  			`// +build foo
   316  
   317  // +build bar
   318  
   319  package main`,
   320  			&buildTags{
   321  				expr:    mustParseBuildTag(t, "foo && bar"),
   322  				rawTags: []string{"foo", "bar"},
   323  			},
   324  		},
   325  		{
   326  			"Basic go:build",
   327  			`//go:build foo && bar
   328  
   329  package main`,
   330  			&buildTags{
   331  				expr:    mustParseBuildTag(t, "foo && bar"),
   332  				rawTags: []string{"foo", "bar"},
   333  			},
   334  		},
   335  		{
   336  			"Both go:build and +build",
   337  			`//go:build foo && bar
   338  // +build foo,bar
   339  
   340  package main`,
   341  			&buildTags{
   342  				expr:    mustParseBuildTag(t, "foo && bar"),
   343  				rawTags: []string{"foo", "bar"},
   344  			},
   345  		},
   346  		{
   347  			"comment with space",
   348  			"  //   +build   foo   bar  \n\n",
   349  			&buildTags{
   350  				expr:    mustParseBuildTag(t, "foo || bar"),
   351  				rawTags: []string{"foo", "bar"},
   352  			},
   353  		},
   354  		{
   355  			"slash star comment",
   356  			"/* +build foo */\n\n",
   357  			nil,
   358  		},
   359  	} {
   360  		t.Run(tc.desc, func(t *testing.T) {
   361  			f, err := os.CreateTemp(".", "TestReadTags")
   362  			if err != nil {
   363  				t.Fatal(err)
   364  			}
   365  			path := f.Name()
   366  			defer os.Remove(path)
   367  
   368  			if _, err := f.WriteString(tc.source); err != nil {
   369  				t.Fatal(err)
   370  			}
   371  
   372  			if got, err := readTags(path); err != nil {
   373  				t.Fatal(err)
   374  			} else if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" {
   375  				t.Errorf("(-want, +got): %s", diff)
   376  			}
   377  		})
   378  	}
   379  }
   380  
   381  func TestCheckConstraints(t *testing.T) {
   382  	dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCheckConstraints")
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	defer os.RemoveAll(dir)
   387  	for _, tc := range []struct {
   388  		desc                        string
   389  		genericTags                 map[string]bool
   390  		os, arch, filename, content string
   391  		want                        bool
   392  	}{
   393  		{
   394  			desc: "unconstrained",
   395  			want: true,
   396  		}, {
   397  			desc:     "goos satisfied",
   398  			filename: "foo_linux.go",
   399  			os:       "linux",
   400  			want:     true,
   401  		}, {
   402  			desc:     "goos unsatisfied",
   403  			filename: "foo_linux.go",
   404  			os:       "darwin",
   405  			want:     false,
   406  		}, {
   407  			desc:     "goarch satisfied",
   408  			filename: "foo_amd64.go",
   409  			arch:     "amd64",
   410  			want:     true,
   411  		}, {
   412  			desc:     "goarch unsatisfied",
   413  			filename: "foo_amd64.go",
   414  			arch:     "arm",
   415  			want:     false,
   416  		}, {
   417  			desc:     "goos goarch satisfied",
   418  			filename: "foo_linux_amd64.go",
   419  			os:       "linux",
   420  			arch:     "amd64",
   421  			want:     true,
   422  		}, {
   423  			desc:     "goos goarch unsatisfied",
   424  			filename: "foo_linux_amd64.go",
   425  			os:       "darwin",
   426  			arch:     "amd64",
   427  			want:     false,
   428  		}, {
   429  			desc:     "unix filename on darwin",
   430  			filename: "foo_unix.go",
   431  			os:       "darwin",
   432  			want:     true,
   433  		}, {
   434  			desc:     "unix filename on windows",
   435  			filename: "foo_unix.go",
   436  			os:       "windows",
   437  			want:     true,
   438  		}, {
   439  			desc:     "non-unix tag on linux",
   440  			filename: "foo_bar.go",
   441  			os:       "darwin",
   442  			content:  "//go:build !unix\n\npackage foo",
   443  			want:     false,
   444  		}, {
   445  			desc:     "non-unix tag on windows",
   446  			filename: "foo_bar.go",
   447  			os:       "windows",
   448  			content:  "//go:build !unix\n\npackage foo",
   449  			want:     true,
   450  		}, {
   451  			desc:     "unix tag on windows",
   452  			filename: "foo_bar.go",
   453  			os:       "windows",
   454  			content:  "//go:build unix\n\npackage foo",
   455  			want:     false,
   456  		}, {
   457  			desc:     "unix tag on linux",
   458  			filename: "foo_bar.go",
   459  			os:       "linux",
   460  			content:  "//go:build unix\n\npackage foo",
   461  			want:     true,
   462  		}, {
   463  			desc:     "goos unsatisfied tags satisfied",
   464  			filename: "foo_linux.go",
   465  			content:  "// +build foo\n\npackage foo",
   466  			want:     false,
   467  		}, {
   468  			desc:        "tags all satisfied",
   469  			genericTags: map[string]bool{"a": true, "b": true},
   470  			content:     "// +build a,b\n\npackage foo",
   471  			want:        true,
   472  		}, {
   473  			desc:        "tags some satisfied",
   474  			genericTags: map[string]bool{"a": true},
   475  			content:     "// +build a,b\n\npackage foo",
   476  			want:        false,
   477  		}, {
   478  			desc:    "tag unsatisfied negated",
   479  			content: "// +build !a\n\npackage foo",
   480  			want:    true,
   481  		}, {
   482  			desc:        "tag satisfied negated",
   483  			genericTags: map[string]bool{"a": true},
   484  			content:     "// +build !a\n\npackage foo",
   485  			want:        false,
   486  		}, {
   487  			desc:    "tag double negative",
   488  			content: "// +build !!a\n\npackage foo",
   489  			want:    false,
   490  		}, {
   491  			desc:        "tag group and satisfied",
   492  			genericTags: map[string]bool{"foo": true, "bar": true},
   493  			content:     "// +build foo,bar\n\npackage foo",
   494  			want:        true,
   495  		}, {
   496  			desc:        "tag group and unsatisfied",
   497  			genericTags: map[string]bool{"foo": true},
   498  			content:     "// +build foo,bar\n\npackage foo",
   499  			want:        false,
   500  		}, {
   501  			desc:        "tag line or satisfied",
   502  			genericTags: map[string]bool{"foo": true},
   503  			content:     "// +build foo bar\n\npackage foo",
   504  			want:        true,
   505  		}, {
   506  			desc:        "tag line or unsatisfied",
   507  			genericTags: map[string]bool{"foo": true},
   508  			content:     "// +build !foo bar\n\npackage foo",
   509  			want:        false,
   510  		}, {
   511  			desc:        "tag lines and satisfied",
   512  			genericTags: map[string]bool{"foo": true, "bar": true},
   513  			content: `
   514  // +build foo
   515  // +build bar
   516  
   517  package foo`,
   518  			want: true,
   519  		}, {
   520  			desc:        "tag lines and unsatisfied",
   521  			genericTags: map[string]bool{"foo": true},
   522  			content: `
   523  // +build foo
   524  // +build bar
   525  
   526  package foo`,
   527  			want: false,
   528  		}, {
   529  			desc:        "cgo tags satisfied",
   530  			os:          "linux",
   531  			genericTags: map[string]bool{"foo": true},
   532  			content: `
   533  // +build foo
   534  
   535  package foo
   536  
   537  /*
   538  #cgo linux CFLAGS: -Ilinux
   539  */
   540  import "C"
   541  `,
   542  			want: true,
   543  		}, {
   544  			desc: "cgo tags unsatisfied",
   545  			os:   "linux",
   546  			content: `
   547  package foo
   548  
   549  /*
   550  #cgo !linux CFLAGS: -Inotlinux
   551  */
   552  import "C"
   553  `,
   554  			want: false,
   555  		}, {
   556  			desc:    "release tags",
   557  			content: "// +build go1.7,go1.8,go1.9,go1.91,go2.0\n\npackage foo",
   558  			want:    true,
   559  		}, {
   560  			desc:    "release tag negated",
   561  			content: "// +build !go1.8\n\npackage foo",
   562  			want:    true,
   563  		}, {
   564  			desc:    "cgo tag",
   565  			content: "// +build cgo",
   566  			want:    true,
   567  		}, {
   568  			desc:    "cgo tag negated",
   569  			content: "// +build !cgo",
   570  			want:    true,
   571  		}, {
   572  			desc:    "race msan tags",
   573  			content: "// +build msan race",
   574  			want:    true,
   575  		}, {
   576  			desc:    "race msan tags negated",
   577  			content: "//+ build !msan,!race",
   578  			want:    true,
   579  		},
   580  	} {
   581  		t.Run(tc.desc, func(t *testing.T) {
   582  			c, _, _ := testConfig(t)
   583  			gc := getGoConfig(c)
   584  			gc.genericTags = tc.genericTags
   585  			if gc.genericTags == nil {
   586  				gc.genericTags = map[string]bool{"gc": true}
   587  			}
   588  			filename := tc.filename
   589  			if filename == "" {
   590  				filename = tc.desc + ".go"
   591  			}
   592  			content := []byte(tc.content)
   593  			if len(content) == 0 {
   594  				content = []byte(`package foo`)
   595  			}
   596  
   597  			path := filepath.Join(dir, filename)
   598  			if err := os.WriteFile(path, content, 0o666); err != nil {
   599  				t.Fatal(err)
   600  			}
   601  
   602  			fi := goFileInfo(path, "")
   603  			var cgoTags *cgoTagsAndOpts
   604  			if len(fi.copts) > 0 {
   605  				cgoTags = fi.copts[0]
   606  			}
   607  
   608  			got := checkConstraints(c, tc.os, tc.arch, fi.goos, fi.goarch, fi.tags, cgoTags)
   609  			if diff := cmp.Diff(tc.want, got); diff != "" {
   610  				t.Errorf("(-want, +got): %s", diff)
   611  			}
   612  		})
   613  	}
   614  }
   615  
   616  func TestIsOSArchSpecific(t *testing.T) {
   617  	for _, tc := range []struct {
   618  		desc              string
   619  		filename, content string
   620  
   621  		expectOSSpecific   bool
   622  		expectArchSpecific bool
   623  	}{
   624  		{
   625  			desc:               "normal",
   626  			filename:           "foo.go",
   627  			content:            "package foo",
   628  			expectOSSpecific:   false,
   629  			expectArchSpecific: false,
   630  		},
   631  		{
   632  			desc:               "unix directive",
   633  			filename:           "foo.go",
   634  			content:            "//go:build unix\n\npackage foo",
   635  			expectOSSpecific:   true,
   636  			expectArchSpecific: false,
   637  		},
   638  		{
   639  			desc:               "exclude-unix directive",
   640  			filename:           "foo.go",
   641  			content:            "//go:build !unix\n\npackage foo",
   642  			expectOSSpecific:   true,
   643  			expectArchSpecific: false,
   644  		},
   645  		{
   646  			desc:               "arch directive",
   647  			filename:           "foo.go",
   648  			content:            "//go:build arm64\n\npackage foo",
   649  			expectOSSpecific:   false,
   650  			expectArchSpecific: true,
   651  		},
   652  		{
   653  			desc:               "exclude-arch directive",
   654  			filename:           "foo.go",
   655  			content:            "//go:build !arm64\n\npackage foo",
   656  			expectOSSpecific:   false,
   657  			expectArchSpecific: true,
   658  		},
   659  		{
   660  			desc:               "os directive",
   661  			filename:           "foo.go",
   662  			content:            "//go:build linux\n\npackage foo",
   663  			expectOSSpecific:   true,
   664  			expectArchSpecific: false,
   665  		},
   666  		{
   667  			desc:               "exclude-os directive",
   668  			filename:           "foo.go",
   669  			content:            "//go:build !linux\n\npackage foo",
   670  			expectOSSpecific:   true,
   671  			expectArchSpecific: false,
   672  		},
   673  		{
   674  			desc:               "os and arch directive",
   675  			filename:           "foo.go",
   676  			content:            "//go:build linux && amd64\n\npackage foo",
   677  			expectOSSpecific:   true,
   678  			expectArchSpecific: true,
   679  		},
   680  		{
   681  			desc:               "unix and arch directive",
   682  			filename:           "foo.go",
   683  			content:            "//go:build unix && amd64\n\npackage foo",
   684  			expectOSSpecific:   true,
   685  			expectArchSpecific: true,
   686  		},
   687  	} {
   688  		t.Run(tc.desc, func(t *testing.T) {
   689  			tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestIsOSSpecific_*")
   690  			if err != nil {
   691  				t.Fatal(err)
   692  			}
   693  			t.Cleanup(func() {
   694  				os.RemoveAll(tmpDir)
   695  			})
   696  
   697  			path := filepath.Join(tmpDir, tc.filename)
   698  			if err := os.WriteFile(path, []byte(tc.content), 0o666); err != nil {
   699  				t.Fatal(err)
   700  			}
   701  			fi := goFileInfo(path, "")
   702  			var cgoTags *cgoTagsAndOpts
   703  			if len(fi.copts) > 0 {
   704  				cgoTags = fi.copts[0]
   705  			}
   706  
   707  			gotOSSpecific, gotArchSpecific := isOSArchSpecific(fi, cgoTags)
   708  			if diff := cmp.Diff(tc.expectOSSpecific, gotOSSpecific); diff != "" {
   709  				t.Errorf("(-want, +got): %s", diff)
   710  			}
   711  			if diff := cmp.Diff(tc.expectArchSpecific, gotArchSpecific); diff != "" {
   712  				t.Errorf("(-want, +got): %s", diff)
   713  			}
   714  		})
   715  	}
   716  }