| /* Copyright 2017 The Bazel Authors. All rights reserved. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package golang |
| |
| import ( |
| "os" |
| "path/filepath" |
| "testing" |
| |
| "github.com/google/go-cmp/cmp" |
| ) |
| |
| func TestOtherFileInfo(t *testing.T) { |
| dir := "." |
| for _, tc := range []struct { |
| desc, name, source string |
| wantTags *buildTags |
| }{ |
| { |
| "empty file", |
| "foo.c", |
| "", |
| nil, |
| }, |
| { |
| "tags file", |
| "foo.c", |
| `// +build foo bar |
| // +build baz,!ignore |
| |
| `, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "(foo || bar) && (baz && !ignore)"), |
| rawTags: []string{"foo", "bar", "baz", "ignore"}, |
| }, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| if err := os.WriteFile(tc.name, []byte(tc.source), 0o600); err != nil { |
| t.Fatal(err) |
| } |
| defer os.Remove(tc.name) |
| |
| got := otherFileInfo(filepath.Join(dir, tc.name)) |
| |
| // Only check that we can extract tags. Everything else is covered |
| // by other tests. |
| if diff := cmp.Diff(tc.wantTags, got.tags, fileInfoCmpOption); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestFileNameInfo(t *testing.T) { |
| for _, tc := range []struct { |
| desc, name string |
| want fileInfo |
| }{ |
| { |
| "simple go file", |
| "simple.go", |
| fileInfo{ |
| ext: goExt, |
| }, |
| }, |
| { |
| "simple go test", |
| "foo_test.go", |
| fileInfo{ |
| ext: goExt, |
| isTest: true, |
| }, |
| }, |
| { |
| "test source", |
| "test.go", |
| fileInfo{ |
| ext: goExt, |
| isTest: false, |
| }, |
| }, |
| { |
| "_test source", |
| "_test.go", |
| fileInfo{ |
| ext: unknownExt, |
| }, |
| }, |
| { |
| "source with goos", |
| "foo_linux.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "linux", |
| }, |
| }, |
| { |
| "source with goarch", |
| "foo_amd64.go", |
| fileInfo{ |
| ext: goExt, |
| goarch: "amd64", |
| }, |
| }, |
| { |
| "source with goos then goarch", |
| "foo_linux_amd64.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "linux", |
| goarch: "amd64", |
| }, |
| }, |
| { |
| "source with goarch then goos", |
| "foo_amd64_linux.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "linux", |
| }, |
| }, |
| { |
| "test with goos and goarch", |
| "foo_linux_amd64_test.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "linux", |
| goarch: "amd64", |
| isTest: true, |
| }, |
| }, |
| { |
| "test then goos", |
| "foo_test_linux.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "linux", |
| }, |
| }, |
| { |
| "goos source", |
| "linux.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "", |
| }, |
| }, |
| { |
| "goarch source", |
| "amd64.go", |
| fileInfo{ |
| ext: goExt, |
| goarch: "", |
| }, |
| }, |
| { |
| "goos test", |
| "linux_test.go", |
| fileInfo{ |
| ext: goExt, |
| goos: "", |
| isTest: true, |
| }, |
| }, |
| { |
| "c file", |
| "foo_test.cxx", |
| fileInfo{ |
| ext: cExt, |
| isTest: false, |
| }, |
| }, |
| { |
| "c os test file", |
| "foo_linux_test.c", |
| fileInfo{ |
| ext: cExt, |
| isTest: false, |
| goos: "linux", |
| }, |
| }, |
| { |
| "h file", |
| "foo_linux.h", |
| fileInfo{ |
| ext: hExt, |
| goos: "linux", |
| }, |
| }, |
| { |
| "go asm file", |
| "foo_amd64.s", |
| fileInfo{ |
| ext: sExt, |
| goarch: "amd64", |
| }, |
| }, |
| { |
| "c asm file", |
| "foo.S", |
| fileInfo{ |
| ext: csExt, |
| }, |
| }, |
| { |
| "unsupported file", |
| "foo.m", |
| fileInfo{ |
| ext: cExt, |
| }, |
| }, |
| { |
| "ignored test file", |
| "foo_test.py", |
| fileInfo{ |
| isTest: false, |
| }, |
| }, |
| { |
| "ignored xtest file", |
| "foo_xtest.py", |
| fileInfo{ |
| isTest: false, |
| }, |
| }, |
| { |
| "ignored file", |
| "foo.txt", |
| fileInfo{ |
| ext: unknownExt, |
| }, |
| }, |
| { |
| "hidden file", |
| ".foo.go", |
| fileInfo{ |
| ext: unknownExt, |
| }, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| tc.want.name = tc.name |
| tc.want.path = filepath.Join("dir", tc.name) |
| got := fileNameInfo(tc.want.path) |
| if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestReadTags(t *testing.T) { |
| for _, tc := range []struct { |
| desc, source string |
| want *buildTags |
| }{ |
| { |
| "empty file", |
| "", |
| nil, |
| }, |
| { |
| "single comment without blank line", |
| "// +build foo\npackage main", |
| nil, |
| }, |
| { |
| "multiple comments without blank link", |
| `// +build foo |
| |
| // +build bar |
| package main |
| |
| `, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo"), |
| rawTags: []string{"foo"}, |
| }, |
| }, |
| { |
| "single comment", |
| "// +build foo\n\n", |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo"), |
| rawTags: []string{"foo"}, |
| }, |
| }, |
| { |
| "multiple comments", |
| `// +build foo |
| // +build bar |
| |
| package main`, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo && bar"), |
| rawTags: []string{"foo", "bar"}, |
| }, |
| }, |
| { |
| "multiple comments with blank", |
| `// +build foo |
| |
| // +build bar |
| |
| package main`, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo && bar"), |
| rawTags: []string{"foo", "bar"}, |
| }, |
| }, |
| { |
| "Basic go:build", |
| `//go:build foo && bar |
| |
| package main`, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo && bar"), |
| rawTags: []string{"foo", "bar"}, |
| }, |
| }, |
| { |
| "Both go:build and +build", |
| `//go:build foo && bar |
| // +build foo,bar |
| |
| package main`, |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo && bar"), |
| rawTags: []string{"foo", "bar"}, |
| }, |
| }, |
| { |
| "comment with space", |
| " // +build foo bar \n\n", |
| &buildTags{ |
| expr: mustParseBuildTag(t, "foo || bar"), |
| rawTags: []string{"foo", "bar"}, |
| }, |
| }, |
| { |
| "slash star comment", |
| "/* +build foo */\n\n", |
| nil, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| f, err := os.CreateTemp(".", "TestReadTags") |
| if err != nil { |
| t.Fatal(err) |
| } |
| path := f.Name() |
| defer os.Remove(path) |
| |
| if _, err := f.WriteString(tc.source); err != nil { |
| t.Fatal(err) |
| } |
| |
| if got, err := readTags(path); err != nil { |
| t.Fatal(err) |
| } else if diff := cmp.Diff(tc.want, got, fileInfoCmpOption); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestCheckConstraints(t *testing.T) { |
| dir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestCheckConstraints") |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer os.RemoveAll(dir) |
| for _, tc := range []struct { |
| desc string |
| genericTags map[string]bool |
| os, arch, filename, content string |
| want bool |
| }{ |
| { |
| desc: "unconstrained", |
| want: true, |
| }, { |
| desc: "goos satisfied", |
| filename: "foo_linux.go", |
| os: "linux", |
| want: true, |
| }, { |
| desc: "goos unsatisfied", |
| filename: "foo_linux.go", |
| os: "darwin", |
| want: false, |
| }, { |
| desc: "goarch satisfied", |
| filename: "foo_amd64.go", |
| arch: "amd64", |
| want: true, |
| }, { |
| desc: "goarch unsatisfied", |
| filename: "foo_amd64.go", |
| arch: "arm", |
| want: false, |
| }, { |
| desc: "goos goarch satisfied", |
| filename: "foo_linux_amd64.go", |
| os: "linux", |
| arch: "amd64", |
| want: true, |
| }, { |
| desc: "goos goarch unsatisfied", |
| filename: "foo_linux_amd64.go", |
| os: "darwin", |
| arch: "amd64", |
| want: false, |
| }, { |
| desc: "unix filename on darwin", |
| filename: "foo_unix.go", |
| os: "darwin", |
| want: true, |
| }, { |
| desc: "unix filename on windows", |
| filename: "foo_unix.go", |
| os: "windows", |
| want: true, |
| }, { |
| desc: "non-unix tag on linux", |
| filename: "foo_bar.go", |
| os: "darwin", |
| content: "//go:build !unix\n\npackage foo", |
| want: false, |
| }, { |
| desc: "non-unix tag on windows", |
| filename: "foo_bar.go", |
| os: "windows", |
| content: "//go:build !unix\n\npackage foo", |
| want: true, |
| }, { |
| desc: "unix tag on windows", |
| filename: "foo_bar.go", |
| os: "windows", |
| content: "//go:build unix\n\npackage foo", |
| want: false, |
| }, { |
| desc: "unix tag on linux", |
| filename: "foo_bar.go", |
| os: "linux", |
| content: "//go:build unix\n\npackage foo", |
| want: true, |
| }, { |
| desc: "goos unsatisfied tags satisfied", |
| filename: "foo_linux.go", |
| content: "// +build foo\n\npackage foo", |
| want: false, |
| }, { |
| desc: "tags all satisfied", |
| genericTags: map[string]bool{"a": true, "b": true}, |
| content: "// +build a,b\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tags some satisfied", |
| genericTags: map[string]bool{"a": true}, |
| content: "// +build a,b\n\npackage foo", |
| want: false, |
| }, { |
| desc: "tag unsatisfied negated", |
| content: "// +build !a\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tag satisfied negated", |
| genericTags: map[string]bool{"a": true}, |
| content: "// +build !a\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tag double negative", |
| content: "// +build !!a\n\npackage foo", |
| want: false, |
| }, { |
| desc: "tag group and satisfied", |
| genericTags: map[string]bool{"foo": true, "bar": true}, |
| content: "// +build foo,bar\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tag group and unsatisfied", |
| genericTags: map[string]bool{"foo": true}, |
| content: "// +build foo,bar\n\npackage foo", |
| want: false, |
| }, { |
| desc: "tag line or satisfied", |
| genericTags: map[string]bool{"foo": true}, |
| content: "// +build foo bar\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tag line or unsatisfied", |
| genericTags: map[string]bool{"foo": true}, |
| content: "// +build !foo bar\n\npackage foo", |
| want: true, |
| }, { |
| desc: "tag lines and satisfied", |
| genericTags: map[string]bool{"foo": true, "bar": true}, |
| content: ` |
| // +build foo |
| // +build bar |
| |
| package foo`, |
| want: true, |
| }, { |
| desc: "tag lines and unsatisfied", |
| genericTags: map[string]bool{"foo": true}, |
| content: ` |
| // +build foo |
| // +build bar |
| |
| package foo`, |
| want: false, |
| }, { |
| desc: "cgo tags satisfied", |
| os: "linux", |
| genericTags: map[string]bool{"foo": true}, |
| content: ` |
| // +build foo |
| |
| package foo |
| |
| /* |
| #cgo linux CFLAGS: -Ilinux |
| */ |
| import "C" |
| `, |
| want: true, |
| }, { |
| desc: "cgo tags unsatisfied", |
| os: "linux", |
| content: ` |
| package foo |
| |
| /* |
| #cgo !linux CFLAGS: -Inotlinux |
| */ |
| import "C" |
| `, |
| want: false, |
| }, { |
| desc: "release tags", |
| content: "// +build go1.7,go1.8,go1.9,go1.91,go2.0\n\npackage foo", |
| want: true, |
| }, { |
| desc: "release tag negated", |
| content: "// +build !go1.8\n\npackage foo", |
| want: true, |
| }, { |
| desc: "cgo tag", |
| content: "// +build cgo", |
| want: true, |
| }, { |
| desc: "cgo tag negated", |
| content: "// +build !cgo", |
| want: true, |
| }, { |
| desc: "race msan tags", |
| content: "// +build msan race", |
| want: true, |
| }, { |
| desc: "race msan tags negated", |
| content: "//+ build !msan,!race", |
| want: true, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| c, _, _ := testConfig(t) |
| gc := getGoConfig(c) |
| gc.genericTags = tc.genericTags |
| if gc.genericTags == nil { |
| gc.genericTags = map[string]bool{"gc": true} |
| } |
| filename := tc.filename |
| if filename == "" { |
| filename = tc.desc + ".go" |
| } |
| content := []byte(tc.content) |
| if len(content) == 0 { |
| content = []byte(`package foo`) |
| } |
| |
| path := filepath.Join(dir, filename) |
| if err := os.WriteFile(path, content, 0o666); err != nil { |
| t.Fatal(err) |
| } |
| |
| fi := goFileInfo(path, "") |
| var cgoTags *cgoTagsAndOpts |
| if len(fi.copts) > 0 { |
| cgoTags = fi.copts[0] |
| } |
| |
| got := checkConstraints(c, tc.os, tc.arch, fi.goos, fi.goarch, fi.tags, cgoTags) |
| if diff := cmp.Diff(tc.want, got); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| }) |
| } |
| } |
| |
| func TestIsOSArchSpecific(t *testing.T) { |
| for _, tc := range []struct { |
| desc string |
| filename, content string |
| |
| expectOSSpecific bool |
| expectArchSpecific bool |
| }{ |
| { |
| desc: "normal", |
| filename: "foo.go", |
| content: "package foo", |
| expectOSSpecific: false, |
| expectArchSpecific: false, |
| }, |
| { |
| desc: "unix directive", |
| filename: "foo.go", |
| content: "//go:build unix\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: false, |
| }, |
| { |
| desc: "exclude-unix directive", |
| filename: "foo.go", |
| content: "//go:build !unix\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: false, |
| }, |
| { |
| desc: "arch directive", |
| filename: "foo.go", |
| content: "//go:build arm64\n\npackage foo", |
| expectOSSpecific: false, |
| expectArchSpecific: true, |
| }, |
| { |
| desc: "exclude-arch directive", |
| filename: "foo.go", |
| content: "//go:build !arm64\n\npackage foo", |
| expectOSSpecific: false, |
| expectArchSpecific: true, |
| }, |
| { |
| desc: "os directive", |
| filename: "foo.go", |
| content: "//go:build linux\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: false, |
| }, |
| { |
| desc: "exclude-os directive", |
| filename: "foo.go", |
| content: "//go:build !linux\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: false, |
| }, |
| { |
| desc: "os and arch directive", |
| filename: "foo.go", |
| content: "//go:build linux && amd64\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: true, |
| }, |
| { |
| desc: "unix and arch directive", |
| filename: "foo.go", |
| content: "//go:build unix && amd64\n\npackage foo", |
| expectOSSpecific: true, |
| expectArchSpecific: true, |
| }, |
| } { |
| t.Run(tc.desc, func(t *testing.T) { |
| tmpDir, err := os.MkdirTemp(os.Getenv("TEST_TEMPDIR"), "TestIsOSSpecific_*") |
| if err != nil { |
| t.Fatal(err) |
| } |
| t.Cleanup(func() { |
| os.RemoveAll(tmpDir) |
| }) |
| |
| path := filepath.Join(tmpDir, tc.filename) |
| if err := os.WriteFile(path, []byte(tc.content), 0o666); err != nil { |
| t.Fatal(err) |
| } |
| fi := goFileInfo(path, "") |
| var cgoTags *cgoTagsAndOpts |
| if len(fi.copts) > 0 { |
| cgoTags = fi.copts[0] |
| } |
| |
| gotOSSpecific, gotArchSpecific := isOSArchSpecific(fi, cgoTags) |
| if diff := cmp.Diff(tc.expectOSSpecific, gotOSSpecific); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| if diff := cmp.Diff(tc.expectArchSpecific, gotArchSpecific); diff != "" { |
| t.Errorf("(-want, +got): %s", diff) |
| } |
| }) |
| } |
| } |