225 lines
6.3 KiB
Go
225 lines
6.3 KiB
Go
// Copyright 2021 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package buildinfo_test
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/buildinfo"
|
|
"flag"
|
|
"internal/testenv"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
var flagAll = flag.Bool("all", false, "test all supported GOOS/GOARCH platforms, instead of only the current platform")
|
|
|
|
// TestReadFile confirms that ReadFile can read build information from binaries
|
|
// on supported target platforms. It builds a trivial binary on the current
|
|
// platforms (or all platforms if -all is set) in various configurations and
|
|
// checks that build information can or cannot be read.
|
|
func TestReadFile(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("test requires compiling and linking, which may be slow")
|
|
}
|
|
testenv.MustHaveGoBuild(t)
|
|
|
|
type platform struct{ goos, goarch string }
|
|
platforms := []platform{
|
|
{"aix", "ppc64"},
|
|
{"darwin", "amd64"},
|
|
{"darwin", "arm64"},
|
|
{"linux", "386"},
|
|
{"linux", "amd64"},
|
|
{"windows", "386"},
|
|
{"windows", "amd64"},
|
|
}
|
|
runtimePlatform := platform{runtime.GOOS, runtime.GOARCH}
|
|
haveRuntimePlatform := false
|
|
for _, p := range platforms {
|
|
if p == runtimePlatform {
|
|
haveRuntimePlatform = true
|
|
break
|
|
}
|
|
}
|
|
if !haveRuntimePlatform {
|
|
platforms = append(platforms, runtimePlatform)
|
|
}
|
|
|
|
buildWithModules := func(t *testing.T, goos, goarch string) string {
|
|
dir := t.TempDir()
|
|
gomodPath := filepath.Join(dir, "go.mod")
|
|
gomodData := []byte("module example.com/m\ngo 1.18\n")
|
|
if err := os.WriteFile(gomodPath, gomodData, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
helloPath := filepath.Join(dir, "hello.go")
|
|
helloData := []byte("package main\nfunc main() {}\n")
|
|
if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outPath := filepath.Join(dir, path.Base(t.Name()))
|
|
cmd := exec.Command("go", "build", "-o="+outPath)
|
|
cmd.Dir = dir
|
|
cmd.Env = append(os.Environ(), "GO111MODULE=on", "GOOS="+goos, "GOARCH="+goarch)
|
|
stderr := &bytes.Buffer{}
|
|
cmd.Stderr = stderr
|
|
if err := cmd.Run(); err != nil {
|
|
t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
|
|
}
|
|
return outPath
|
|
}
|
|
|
|
buildWithGOPATH := func(t *testing.T, goos, goarch string) string {
|
|
gopathDir := t.TempDir()
|
|
pkgDir := filepath.Join(gopathDir, "src/example.com/m")
|
|
if err := os.MkdirAll(pkgDir, 0777); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
helloPath := filepath.Join(pkgDir, "hello.go")
|
|
helloData := []byte("package main\nfunc main() {}\n")
|
|
if err := os.WriteFile(helloPath, helloData, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outPath := filepath.Join(gopathDir, path.Base(t.Name()))
|
|
cmd := exec.Command("go", "build", "-o="+outPath)
|
|
cmd.Dir = pkgDir
|
|
cmd.Env = append(os.Environ(), "GO111MODULE=off", "GOPATH="+gopathDir, "GOOS="+goos, "GOARCH="+goarch)
|
|
stderr := &bytes.Buffer{}
|
|
cmd.Stderr = stderr
|
|
if err := cmd.Run(); err != nil {
|
|
t.Fatalf("failed building test file: %v\n%s", err, stderr.Bytes())
|
|
}
|
|
return outPath
|
|
}
|
|
|
|
damageBuildInfo := func(t *testing.T, name string) {
|
|
data, err := os.ReadFile(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
i := bytes.Index(data, []byte("\xff Go buildinf:"))
|
|
if i < 0 {
|
|
t.Fatal("Go buildinf not found")
|
|
}
|
|
data[i+2] = 'N'
|
|
if err := os.WriteFile(name, data, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
goVersionRe := regexp.MustCompile("(?m)^go\t.*\n")
|
|
buildRe := regexp.MustCompile("(?m)^build\t.*\n")
|
|
cleanOutputForComparison := func(got string) string {
|
|
// Remove or replace anything that might depend on the test's environment
|
|
// so we can check the output afterward with a string comparison.
|
|
// We'll remove all build lines except the compiler, just to make sure
|
|
// build lines are included.
|
|
got = goVersionRe.ReplaceAllString(got, "go\tGOVERSION\n")
|
|
got = buildRe.ReplaceAllStringFunc(got, func(match string) string {
|
|
if strings.HasPrefix(match, "build\t-compiler=") {
|
|
return match
|
|
}
|
|
return ""
|
|
})
|
|
return got
|
|
}
|
|
|
|
cases := []struct {
|
|
name string
|
|
build func(t *testing.T, goos, goarch string) string
|
|
want string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "doesnotexist",
|
|
build: func(t *testing.T, goos, goarch string) string {
|
|
return "doesnotexist.txt"
|
|
},
|
|
wantErr: "doesnotexist",
|
|
},
|
|
{
|
|
name: "empty",
|
|
build: func(t *testing.T, _, _ string) string {
|
|
dir := t.TempDir()
|
|
name := filepath.Join(dir, "empty")
|
|
if err := os.WriteFile(name, nil, 0666); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return name
|
|
},
|
|
wantErr: "unrecognized file format",
|
|
},
|
|
{
|
|
name: "valid_modules",
|
|
build: buildWithModules,
|
|
want: "go\tGOVERSION\n" +
|
|
"path\texample.com/m\n" +
|
|
"mod\texample.com/m\t(devel)\t\n" +
|
|
"build\t-compiler=gc\n",
|
|
},
|
|
{
|
|
name: "invalid_modules",
|
|
build: func(t *testing.T, goos, goarch string) string {
|
|
name := buildWithModules(t, goos, goarch)
|
|
damageBuildInfo(t, name)
|
|
return name
|
|
},
|
|
wantErr: "not a Go executable",
|
|
},
|
|
{
|
|
name: "valid_gopath",
|
|
build: buildWithGOPATH,
|
|
want: "go\tGOVERSION\n" +
|
|
"path\texample.com/m\n" +
|
|
"build\t-compiler=gc\n",
|
|
},
|
|
{
|
|
name: "invalid_gopath",
|
|
build: func(t *testing.T, goos, goarch string) string {
|
|
name := buildWithGOPATH(t, goos, goarch)
|
|
damageBuildInfo(t, name)
|
|
return name
|
|
},
|
|
wantErr: "not a Go executable",
|
|
},
|
|
}
|
|
|
|
for _, p := range platforms {
|
|
p := p
|
|
t.Run(p.goos+"_"+p.goarch, func(t *testing.T) {
|
|
if p != runtimePlatform && !*flagAll {
|
|
t.Skipf("skipping platforms other than %s_%s because -all was not set", runtimePlatform.goos, runtimePlatform.goarch)
|
|
}
|
|
for _, tc := range cases {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
name := tc.build(t, p.goos, p.goarch)
|
|
if info, err := buildinfo.ReadFile(name); err != nil {
|
|
if tc.wantErr == "" {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
} else if errMsg := err.Error(); !strings.Contains(errMsg, tc.wantErr) {
|
|
t.Fatalf("got error %q; want error containing %q", errMsg, tc.wantErr)
|
|
}
|
|
} else {
|
|
if tc.wantErr != "" {
|
|
t.Fatalf("unexpected success; want error containing %q", tc.wantErr)
|
|
}
|
|
got := info.String()
|
|
if clean := cleanOutputForComparison(string(got)); got != tc.want && clean != tc.want {
|
|
t.Fatalf("got:\n%s\nwant:\n%s", got, tc.want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|