1 // Copyright 2017 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
26 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
32 var GOOS, GOARCH, GOROOT string
33 var installdir, androiddir string
34 var libSuffix, libgoname string
36 func TestMain(m *testing.M) {
40 func testMain(m *testing.M) int {
41 log.SetFlags(log.Lshortfile)
43 if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
44 fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
47 if runtime.GOOS == "linux" {
48 if _, err := os.Stat("/etc/alpine-release"); err == nil {
49 fmt.Printf("SKIP - skipping failing test on alpine - go.dev/issue/19938\n")
55 GOARCH = goEnv("GOARCH")
56 GOROOT = goEnv("GOROOT")
58 if _, err := os.Stat(GOROOT); os.IsNotExist(err) {
59 log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT)
62 androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid())
63 if runtime.GOOS != GOOS && GOOS == "android" {
64 args := append(adbCmd(), "exec-out", "mkdir", "-p", androiddir)
65 cmd := exec.Command(args[0], args[1:]...)
66 out, err := cmd.CombinedOutput()
68 log.Fatalf("setupAndroid failed: %v\n%s\n", err, out)
70 defer cleanupAndroid()
73 cc = []string{goEnv("CC")}
75 out := goEnv("GOGCCFLAGS")
82 if quote == '\000' && unicode.IsSpace(c) {
84 cc = append(cc, s[start:i])
92 if quote == '\000' && !backslash && (c == '"' || c == '\'') {
95 } else if !backslash && quote == c {
97 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
105 cc = append(cc, s[start:])
109 case "darwin", "ios":
111 // TODO(crawshaw): can we do better?
112 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...)
114 cc = append(cc, "-pie")
116 libgodir := GOOS + "_" + GOARCH
118 case "darwin", "ios":
119 if GOARCH == "arm64" {
120 libgodir += "_shared"
122 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
123 libgodir += "_shared"
125 cc = append(cc, "-I", filepath.Join("pkg", libgodir))
127 // Force reallocation (and avoid aliasing bugs) for parallel tests that append to cc.
128 cc = cc[:len(cc):len(cc)]
130 if GOOS == "windows" {
134 // Copy testdata into GOPATH/src/testcshared, along with a go.mod file
135 // declaring the same path.
137 GOPATH, err := os.MkdirTemp("", "cshared_test")
141 defer os.RemoveAll(GOPATH)
142 os.Setenv("GOPATH", GOPATH)
144 modRoot := filepath.Join(GOPATH, "src", "testcshared")
145 if err := overlayDir(modRoot, "testdata"); err != nil {
148 if err := os.Chdir(modRoot); err != nil {
151 os.Setenv("PWD", modRoot)
152 if err := os.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil {
156 // Directory where cgo headers and outputs will be installed.
157 // The installation directory format varies depending on the platform.
158 output, err := exec.Command("go", "list",
159 "-buildmode=c-shared",
161 "runtime/cgo").CombinedOutput()
163 log.Panicf("go list failed: %v\n%s", err, output)
165 runtimeCgoTarget := string(bytes.TrimSpace(output))
166 libSuffix = strings.TrimPrefix(filepath.Ext(runtimeCgoTarget), ".")
169 if installdir != "" {
170 err := os.RemoveAll(installdir)
180 func goEnv(key string) string {
181 out, err := exec.Command("go", "env", key).Output()
183 log.Printf("go env %s failed:\n%s", key, err)
184 log.Panicf("%s", err.(*exec.ExitError).Stderr)
186 return strings.TrimSpace(string(out))
189 func cmdToRun(name string) string {
190 return "./" + name + exeSuffix
193 func adbCmd() []string {
194 cmd := []string{"adb"}
195 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" {
196 cmd = append(cmd, strings.Split(flags, " ")...)
201 func adbPush(t *testing.T, filename string) {
202 if runtime.GOOS == GOOS || GOOS != "android" {
205 args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename))
206 cmd := exec.Command(args[0], args[1:]...)
207 if out, err := cmd.CombinedOutput(); err != nil {
208 t.Fatalf("adb command failed: %v\n%s\n", err, out)
212 func adbRun(t *testing.T, env []string, adbargs ...string) string {
213 if GOOS != "android" {
214 t.Fatalf("trying to run adb command when operating system is not android.")
216 args := append(adbCmd(), "exec-out")
217 // Propagate LD_LIBRARY_PATH to the adb shell invocation.
218 for _, e := range env {
219 if strings.Contains(e, "LD_LIBRARY_PATH=") {
220 adbargs = append([]string{e}, adbargs...)
224 shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " "))
225 args = append(args, shellcmd)
226 cmd := exec.Command(args[0], args[1:]...)
227 out, err := cmd.CombinedOutput()
229 t.Fatalf("adb command failed: %v\n%s\n", err, out)
231 return strings.Replace(string(out), "\r", "", -1)
234 func run(t *testing.T, extraEnv []string, args ...string) string {
236 cmd := exec.Command(args[0], args[1:]...)
237 if len(extraEnv) > 0 {
238 cmd.Env = append(os.Environ(), extraEnv...)
241 if GOOS != "windows" {
242 // TestUnexportedSymbols relies on file descriptor 30
243 // being closed when the program starts, so enforce
244 // that in all cases. (The first three descriptors are
245 // stdin/stdout/stderr, so we just need to make sure
246 // that cmd.ExtraFiles[27] exists and is nil.)
247 cmd.ExtraFiles = make([]*os.File, 28)
250 out, err := cmd.CombinedOutput()
252 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out)
254 t.Logf("run: %v", args)
259 func runExe(t *testing.T, extraEnv []string, args ...string) string {
261 if runtime.GOOS != GOOS && GOOS == "android" {
262 return adbRun(t, append(os.Environ(), extraEnv...), args...)
264 return run(t, extraEnv, args...)
267 func runCC(t *testing.T, args ...string) string {
269 // This function is run in parallel, so append to a copy of cc
270 // rather than cc itself.
271 return run(t, nil, append(append([]string(nil), cc...), args...)...)
274 func createHeaders() error {
275 // The 'cgo' command generates a number of additional artifacts,
276 // but we're only interested in the header.
277 // Shunt the rest of the outputs to a temporary directory.
278 objDir, err := os.MkdirTemp("", "testcshared_obj")
282 defer os.RemoveAll(objDir)
284 // Generate a C header file for p, which is a non-main dependency
285 // of main package libgo.
287 // TODO(golang.org/issue/35715): This should be simpler.
288 args := []string{"go", "tool", "cgo",
290 "-exportheader", "p.h",
291 filepath.Join(".", "p", "p.go")}
292 cmd := exec.Command(args[0], args[1:]...)
293 out, err := cmd.CombinedOutput()
295 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
298 // Generate a C header file for libgo itself.
299 installdir, err = os.MkdirTemp("", "testcshared")
303 libgoname = "libgo." + libSuffix
305 args = []string{"go", "build", "-buildmode=c-shared", "-o", filepath.Join(installdir, libgoname), "./libgo"}
306 cmd = exec.Command(args[0], args[1:]...)
307 out, err = cmd.CombinedOutput()
309 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
312 args = []string{"go", "build", "-buildmode=c-shared",
313 "-installsuffix", "testcshared",
315 filepath.Join(".", "libgo", "libgo.go")}
316 if GOOS == "windows" && strings.HasSuffix(args[6], ".a") {
317 args[6] = strings.TrimSuffix(args[6], ".a") + ".dll"
319 cmd = exec.Command(args[0], args[1:]...)
320 out, err = cmd.CombinedOutput()
322 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out)
324 if GOOS == "windows" {
325 // We can't simply pass -Wl,--out-implib, because this relies on having imports from multiple packages,
326 // which results in the linkers output implib getting overwritten at each step. So instead build the
327 // import library the traditional way, using a def file.
328 err = os.WriteFile("libgo.def",
329 []byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"),
332 return fmt.Errorf("unable to write def file: %v", err)
334 out, err = exec.Command(cc[0], append(cc[1:], "-print-prog-name=dlltool")...).CombinedOutput()
336 return fmt.Errorf("unable to find dlltool path: %v\n%s\n", err, out)
338 dlltoolpath := strings.TrimSpace(string(out))
339 if filepath.Ext(dlltoolpath) == "" {
340 // Some compilers report slash-separated paths without extensions
341 // instead of ordinary Windows paths.
342 // Try to find the canonical name for the path.
343 if lp, err := exec.LookPath(dlltoolpath); err == nil {
348 args := []string{dlltoolpath, "-D", args[6], "-l", libgoname, "-d", "libgo.def"}
350 if filepath.Ext(dlltoolpath) == "" {
351 // This is an unfortunate workaround for
352 // https://github.com/mstorsjo/llvm-mingw/issues/205 in which
353 // we basically reimplement the contents of the dlltool.sh
354 // wrapper: https://git.io/JZFlU.
355 // TODO(thanm): remove this workaround once we can upgrade
356 // the compilers on the windows-arm64 builder.
357 dlltoolContents, err := os.ReadFile(args[0])
359 return fmt.Errorf("unable to read dlltool: %v\n", err)
361 if bytes.HasPrefix(dlltoolContents, []byte("#!/bin/sh")) && bytes.Contains(dlltoolContents, []byte("llvm-dlltool")) {
362 base, name := filepath.Split(args[0])
363 args[0] = filepath.Join(base, "llvm-dlltool")
365 switch prefix, _, _ := strings.Cut(name, "-"); prefix {
369 machine = "i386:x86-64"
375 if len(machine) > 0 {
376 args = append(args, "-m", machine)
381 out, err = exec.Command(args[0], args[1:]...).CombinedOutput()
383 return fmt.Errorf("unable to run dlltool to create import library: %v\n%s\n", err, out)
387 if runtime.GOOS != GOOS && GOOS == "android" {
388 args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname))
389 cmd = exec.Command(args[0], args[1:]...)
390 out, err = cmd.CombinedOutput()
392 return fmt.Errorf("adb command failed: %v\n%s\n", err, out)
400 headersOnce sync.Once
404 func createHeadersOnce(t *testing.T) {
405 headersOnce.Do(func() {
406 headersErr = createHeaders()
408 if headersErr != nil {
414 func cleanupAndroid() {
415 if GOOS != "android" {
418 args := append(adbCmd(), "exec-out", "rm", "-rf", androiddir)
419 cmd := exec.Command(args[0], args[1:]...)
420 out, err := cmd.CombinedOutput()
422 log.Panicf("cleanupAndroid failed: %v\n%s\n", err, out)
426 // test0: exported symbols in shared lib are accessible.
427 func TestExportedSymbols(t *testing.T) {
435 runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname)
440 out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
441 if strings.TrimSpace(out) != "PASS" {
446 func checkNumberOfExportedFunctionsWindows(t *testing.T, exportAllSymbols bool) {
466 tmpdir := t.TempDir()
468 srcfile := filepath.Join(tmpdir, "test.go")
469 objfile := filepath.Join(tmpdir, "test.dll")
470 if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil {
473 argv := []string{"build", "-buildmode=c-shared"}
474 if exportAllSymbols {
475 argv = append(argv, "-ldflags", "-extldflags=-Wl,--export-all-symbols")
477 argv = append(argv, "-o", objfile, srcfile)
478 out, err := exec.Command("go", argv...).CombinedOutput()
480 t.Fatalf("build failure: %s\n%s\n", err, string(out))
483 f, err := pe.Open(objfile)
485 t.Fatalf("pe.Open failed: %v", err)
488 section := f.Section(".edata")
490 t.Skip(".edata section is not present")
493 // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go
494 type IMAGE_EXPORT_DIRECTORY struct {
498 NumberOfFunctions uint32
502 var e IMAGE_EXPORT_DIRECTORY
503 if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil {
504 t.Fatalf("binary.Read failed: %v", err)
507 // Only the two exported functions and _cgo_dummy_export should be exported
508 expectedNumber := uint32(3)
510 if exportAllSymbols {
511 if e.NumberOfFunctions <= expectedNumber {
512 t.Fatalf("missing exported functions: %v", e.NumberOfFunctions)
514 if e.NumberOfNames <= expectedNumber {
515 t.Fatalf("missing exported names: %v", e.NumberOfNames)
518 if e.NumberOfFunctions != expectedNumber {
519 t.Fatalf("got %d exported functions; want %d", e.NumberOfFunctions, expectedNumber)
521 if e.NumberOfNames != expectedNumber {
522 t.Fatalf("got %d exported names; want %d", e.NumberOfNames, expectedNumber)
527 func TestNumberOfExportedFunctions(t *testing.T) {
528 if GOOS != "windows" {
529 t.Skip("skipping windows only test")
533 t.Run("OnlyExported", func(t *testing.T) {
534 checkNumberOfExportedFunctionsWindows(t, false)
536 t.Run("All", func(t *testing.T) {
537 checkNumberOfExportedFunctionsWindows(t, true)
541 // test1: shared library can be dynamically loaded and exported symbols are accessible.
542 func TestExportedSymbolsWithDynamicLoad(t *testing.T) {
545 if GOOS == "windows" {
546 t.Logf("Skipping on %s", GOOS)
555 if GOOS != "freebsd" {
556 runCC(t, "-o", cmd, "main1.c", "-ldl")
558 runCC(t, "-o", cmd, "main1.c")
564 out := runExe(t, nil, bin, "./"+libgoname)
565 if strings.TrimSpace(out) != "PASS" {
570 // test2: tests libgo2 which does not export any functions.
571 func TestUnexportedSymbols(t *testing.T) {
574 if GOOS == "windows" {
575 t.Logf("Skipping on %s", GOOS)
581 libname := "libgo2." + libSuffix
586 "-buildmode=c-shared",
587 "-installsuffix", "testcshared",
588 "-o", libname, "./libgo2",
592 linkFlags := "-Wl,--no-as-needed"
593 if GOOS == "darwin" || GOOS == "ios" {
597 runCC(t, "-o", cmd, "main2.c", linkFlags, libname)
600 defer os.Remove(libname)
603 out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin)
605 if strings.TrimSpace(out) != "PASS" {
610 // test3: tests main.main is exported on android.
611 func TestMainExportedOnAndroid(t *testing.T) {
618 t.Logf("Skipping on %s", GOOS)
627 runCC(t, "-o", cmd, "main3.c", "-ldl")
632 out := runExe(t, nil, bin, "./"+libgoname)
633 if strings.TrimSpace(out) != "PASS" {
638 func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) {
639 libname := pkgname + "." + libSuffix
643 "-buildmode=c-shared",
644 "-installsuffix", "testcshared",
645 "-o", libname, pkgname,
648 if GOOS != "freebsd" {
649 runCC(t, "-pthread", "-o", cmd, cfile, "-ldl")
651 runCC(t, "-pthread", "-o", cmd, cfile)
657 defer os.Remove(libname)
659 defer os.Remove(pkgname + ".h")
661 out := runExe(t, nil, bin, "./"+libname)
662 if strings.TrimSpace(out) != "PASS" {
663 t.Error(run(t, nil, bin, libname, "verbose"))
667 // test4: test signal handlers
668 func TestSignalHandlers(t *testing.T) {
670 if GOOS == "windows" {
671 t.Logf("Skipping on %s", GOOS)
674 testSignalHandlers(t, "./libgo4", "main4.c", "testp4")
677 // test5: test signal handlers with os/signal.Notify
678 func TestSignalHandlersWithNotify(t *testing.T) {
680 if GOOS == "windows" {
681 t.Logf("Skipping on %s", GOOS)
684 testSignalHandlers(t, "./libgo5", "main5.c", "testp5")
687 func TestPIE(t *testing.T) {
691 case "linux", "android":
694 t.Logf("Skipping on %s", GOOS)
700 f, err := elf.Open(libgoname)
702 t.Fatalf("elf.Open failed: %v", err)
706 ds := f.SectionByType(elf.SHT_DYNAMIC)
708 t.Fatalf("no SHT_DYNAMIC section")
712 t.Fatalf("can't read SHT_DYNAMIC contents: %v", err)
718 tag = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
721 tag = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
724 if tag == elf.DT_TEXTREL {
725 t.Fatalf("%s has DT_TEXTREL flag", libgoname)
730 // Test that installing a second time recreates the header file.
731 func TestCachedInstall(t *testing.T) {
732 tmpdir, err := os.MkdirTemp("", "cshared")
736 defer os.RemoveAll(tmpdir)
738 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "go.mod"), "go.mod")
739 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "libgo", "libgo.go"), filepath.Join("libgo", "libgo.go"))
740 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "p", "p.go"), filepath.Join("p", "p.go"))
742 buildcmd := []string{"go", "install", "-x", "-buildmode=c-shared", "-installsuffix", "testcshared", "./libgo"}
744 cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
745 cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
746 env := append(cmd.Environ(),
748 "GOBIN="+filepath.Join(tmpdir, "bin"),
749 "GO111MODULE=off", // 'go install' only works in GOPATH mode
753 out, err := cmd.CombinedOutput()
759 var libgoh, ph string
761 walker := func(path string, info os.FileInfo, err error) error {
766 switch filepath.Base(path) {
774 t.Fatalf("%s found again", *ps)
781 if err := filepath.Walk(tmpdir, walker); err != nil {
786 t.Fatal("libgo.h not installed")
789 if err := os.Remove(libgoh); err != nil {
793 cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
794 cmd.Dir = filepath.Join(tmpdir, "src", "testcshared")
797 out, err = cmd.CombinedOutput()
803 if _, err := os.Stat(libgoh); err != nil {
804 t.Errorf("libgo.h not installed in second run: %v", err)
808 // copyFile copies src to dst.
809 func copyFile(t *testing.T, dst, src string) {
811 data, err := os.ReadFile(src)
815 if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil {
818 if err := os.WriteFile(dst, data, 0666); err != nil {
823 func TestGo2C2Go(t *testing.T) {
825 case "darwin", "ios", "windows":
826 // Non-ELF shared libraries don't support the multiple
827 // copies of the runtime package implied by this test.
828 t.Skipf("linking c-shared into Go programs not supported on %s; issue 29061, 49457", GOOS)
830 t.Skip("test fails on android; issue 29087")
835 tmpdir, err := os.MkdirTemp("", "cshared-TestGo2C2Go")
839 defer os.RemoveAll(tmpdir)
841 lib := filepath.Join(tmpdir, "libtestgo2c2go."+libSuffix)
843 if GOOS == "windows" && strings.HasSuffix(lib, ".a") {
844 env = append(env, "CGO_LDFLAGS=-Wl,--out-implib,"+lib, "CGO_LDFLAGS_ALLOW=.*")
845 lib = strings.TrimSuffix(lib, ".a") + ".dll"
847 run(t, env, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go")
849 cgoCflags := os.Getenv("CGO_CFLAGS")
853 cgoCflags += "-I" + tmpdir
855 cgoLdflags := os.Getenv("CGO_LDFLAGS")
856 if cgoLdflags != "" {
859 cgoLdflags += "-L" + tmpdir + " -ltestgo2c2go"
861 goenv := []string{"CGO_CFLAGS=" + cgoCflags, "CGO_LDFLAGS=" + cgoLdflags}
863 ldLibPath := os.Getenv("LD_LIBRARY_PATH")
869 runenv := []string{"LD_LIBRARY_PATH=" + ldLibPath}
871 bin := filepath.Join(tmpdir, "m1") + exeSuffix
872 run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m1")
873 runExe(t, runenv, bin)
875 bin = filepath.Join(tmpdir, "m2") + exeSuffix
876 run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m2")
877 runExe(t, runenv, bin)
880 func TestIssue36233(t *testing.T) {
883 // Test that the export header uses GoComplex64 and GoComplex128
884 // for complex types.
886 tmpdir, err := os.MkdirTemp("", "cshared-TestIssue36233")
890 defer os.RemoveAll(tmpdir)
892 const exportHeader = "issue36233.h"
894 run(t, nil, "go", "tool", "cgo", "-exportheader", exportHeader, "-objdir", tmpdir, "./issue36233/issue36233.go")
895 data, err := os.ReadFile(exportHeader)
900 funcs := []struct{ name, signature string }{
901 {"exportComplex64", "GoComplex64 exportComplex64(GoComplex64 v)"},
902 {"exportComplex128", "GoComplex128 exportComplex128(GoComplex128 v)"},
903 {"exportComplexfloat", "GoComplex64 exportComplexfloat(GoComplex64 v)"},
904 {"exportComplexdouble", "GoComplex128 exportComplexdouble(GoComplex128 v)"},
907 scanner := bufio.NewScanner(bytes.NewReader(data))
911 for _, fn := range funcs {
912 if bytes.Contains(b, []byte(fn.name)) {
914 if !bytes.Contains(b, []byte(fn.signature)) {
915 t.Errorf("function signature mismatch; got %q, want %q", b, fn.signature)
920 if err = scanner.Err(); err != nil {
921 t.Errorf("scanner encountered error: %v", err)
923 if found != len(funcs) {
924 t.Error("missing functions")