blob: a149309c00955bf18ccad52bcff6d64c11d6c49a [file] [log] [blame]
// Copyright 2018 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.
// +build cgo
package packages_test
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/packages/packagestest"
"golang.org/x/tools/internal/testenv"
)
func TestLoadImportsC(t *testing.T) {
// This test checks that when a package depends on the
// test variant of "syscall", "unsafe", or "runtime/cgo", that dependency
// is not removed when those packages are added when it imports "C".
//
// For this test to work, the external test of syscall must have a dependency
// on net, and net must import "syscall" and "C".
if runtime.GOOS == "windows" {
t.Skipf("skipping on windows; packages on windows do not satisfy conditions for test.")
}
if runtime.GOOS == "plan9" {
// See https://golang.org/issue/27100.
t.Skip(`skipping on plan9; for some reason "net [syscall.test]" is not loaded`)
}
testenv.NeedsGoPackages(t)
cfg := &packages.Config{
Context: testCtx,
Mode: packages.LoadImports,
Tests: true,
}
initial, err := packages.Load(cfg, "syscall", "net")
if err != nil {
t.Fatalf("failed to load imports: %v", err)
}
_, all := importGraph(initial)
for _, test := range []struct {
pattern string
wantImport string // an import to check for
}{
{"net", "syscall:syscall"},
{"net [syscall.test]", "syscall:syscall [syscall.test]"},
{"syscall_test [syscall.test]", "net:net [syscall.test]"},
} {
// Test the import paths.
pkg := all[test.pattern]
if pkg == nil {
t.Errorf("package %q not loaded", test.pattern)
continue
}
if imports := strings.Join(imports(pkg), " "); !strings.Contains(imports, test.wantImport) {
t.Errorf("package %q: got \n%s, \nwant to have %s", test.pattern, imports, test.wantImport)
}
}
}
// Stolen from internal/testenv package in core.
// hasGoBuild reports whether the current system can build programs with ``go build''
// and then run them with os.StartProcess or exec.Command.
func hasGoBuild() bool {
if os.Getenv("GO_GCFLAGS") != "" {
// It's too much work to require every caller of the go command
// to pass along "-gcflags="+os.Getenv("GO_GCFLAGS").
// For now, if $GO_GCFLAGS is set, report that we simply can't
// run go build.
return false
}
switch runtime.GOOS {
case "android", "js":
return false
case "darwin":
if strings.HasPrefix(runtime.GOARCH, "arm") {
return false
}
}
return true
}
func TestCgoNoSyntax(t *testing.T) {
packagestest.TestAll(t, testCgoNoSyntax)
}
func testCgoNoSyntax(t *testing.T, exporter packagestest.Exporter) {
// The android builders have a complex setup which causes this test to fail. See discussion on
// golang.org/cl/214943 for more details.
if !hasGoBuild() {
t.Skip("this test can't run on platforms without go build. See discussion on golang.org/cl/214943 for more details.")
}
exported := packagestest.Export(t, exporter, []packagestest.Module{{
Name: "golang.org/fake",
Files: map[string]interface{}{
"c/c.go": `package c; import "C"`,
},
}})
// Explicitly enable cgo.
exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
modes := []packages.LoadMode{
packages.NeedTypes,
packages.NeedName | packages.NeedTypes,
packages.NeedName | packages.NeedTypes | packages.NeedImports,
packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
packages.NeedName | packages.NeedImports,
}
for _, mode := range modes {
t.Run(fmt.Sprint(mode), func(t *testing.T) {
exported.Config.Mode = mode
pkgs, err := packages.Load(exported.Config, "golang.org/fake/c")
if err != nil {
t.Fatal(err)
}
if len(pkgs) != 1 {
t.Fatalf("Expected 1 package, got %v", pkgs)
}
pkg := pkgs[0]
if len(pkg.Errors) != 0 {
t.Fatalf("Expected no errors in package, got %v", pkg.Errors)
}
})
}
}
func TestCgoBadPkgConfig(t *testing.T) {
packagestest.TestAll(t, testCgoBadPkgConfig)
}
func testCgoBadPkgConfig(t *testing.T, exporter packagestest.Exporter) {
if !hasGoBuild() {
t.Skip("this test can't run on platforms without go build. See discussion on golang.org/cl/214943 for more details.")
}
exported := packagestest.Export(t, exporter, []packagestest.Module{{
Name: "golang.org/fake",
Files: map[string]interface{}{
"c/c.go": `package c
// #cgo pkg-config: --cflags -- foo
import "C"`,
},
}})
dir := buildFakePkgconfig(t, exported.Config.Env)
defer os.RemoveAll(dir)
env := exported.Config.Env
for i, v := range env {
if strings.HasPrefix(v, "PATH=") {
env[i] = "PATH=" + dir + string(os.PathListSeparator) + v[len("PATH="):]
}
}
exported.Config.Env = append(exported.Config.Env, "CGO_ENABLED=1")
exported.Config.Mode = packages.NeedName | packages.NeedCompiledGoFiles
pkgs, err := packages.Load(exported.Config, "golang.org/fake/c")
if err != nil {
t.Fatal(err)
}
if len(pkgs) != 1 {
t.Fatalf("Expected 1 package, got %v", pkgs)
}
if pkgs[0].Name != "c" {
t.Fatalf("Expected package to have name \"c\", got %q", pkgs[0].Name)
}
}
func buildFakePkgconfig(t *testing.T, env []string) string {
tmpdir, err := ioutil.TempDir("", "fakepkgconfig")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(filepath.Join(tmpdir, "pkg-config.go"), []byte(`
package main
import "fmt"
import "os"
func main() {
fmt.Fprintln(os.Stderr, "bad")
os.Exit(2)
}
`), 0644)
if err != nil {
os.RemoveAll(tmpdir)
t.Fatal(err)
}
cmd := exec.Command("go", "build", "-o", "pkg-config", "pkg-config.go")
cmd.Dir = tmpdir
cmd.Env = env
if b, err := cmd.CombinedOutput(); err != nil {
os.RemoveAll(tmpdir)
fmt.Println(os.Environ())
t.Log(string(b))
t.Fatal(err)
}
return tmpdir
}