12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229 |
- // Copyright 2016 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 carchive_test
- import (
- "bufio"
- "bytes"
- "debug/elf"
- "flag"
- "fmt"
- "io"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "regexp"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "syscall"
- "testing"
- "time"
- "unicode"
- )
- // Program to run.
- var bin []string
- // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)).
- var cc []string
- // ".exe" on Windows.
- var exeSuffix string
- var GOOS, GOARCH, GOPATH string
- var libgodir string
- var testWork bool // If true, preserve temporary directories.
- func TestMain(m *testing.M) {
- flag.BoolVar(&testWork, "testwork", false, "if true, log and preserve the test's temporary working directory")
- flag.Parse()
- if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" {
- fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n")
- os.Exit(0)
- }
- log.SetFlags(log.Lshortfile)
- os.Exit(testMain(m))
- }
- func testMain(m *testing.M) int {
- // We need a writable GOPATH in which to run the tests.
- // Construct one in a temporary directory.
- var err error
- GOPATH, err = os.MkdirTemp("", "carchive_test")
- if err != nil {
- log.Panic(err)
- }
- if testWork {
- log.Println(GOPATH)
- } else {
- defer os.RemoveAll(GOPATH)
- }
- os.Setenv("GOPATH", GOPATH)
- // Copy testdata into GOPATH/src/testarchive, along with a go.mod file
- // declaring the same path.
- modRoot := filepath.Join(GOPATH, "src", "testcarchive")
- if err := overlayDir(modRoot, "testdata"); err != nil {
- log.Panic(err)
- }
- if err := os.Chdir(modRoot); err != nil {
- log.Panic(err)
- }
- os.Setenv("PWD", modRoot)
- if err := os.WriteFile("go.mod", []byte("module testcarchive\n"), 0666); err != nil {
- log.Panic(err)
- }
- GOOS = goEnv("GOOS")
- GOARCH = goEnv("GOARCH")
- bin = cmdToRun("./testp")
- ccOut := goEnv("CC")
- cc = []string{string(ccOut)}
- out := goEnv("GOGCCFLAGS")
- quote := '\000'
- start := 0
- lastSpace := true
- backslash := false
- s := string(out)
- for i, c := range s {
- if quote == '\000' && unicode.IsSpace(c) {
- if !lastSpace {
- cc = append(cc, s[start:i])
- lastSpace = true
- }
- } else {
- if lastSpace {
- start = i
- lastSpace = false
- }
- if quote == '\000' && !backslash && (c == '"' || c == '\'') {
- quote = c
- backslash = false
- } else if !backslash && quote == c {
- quote = '\000'
- } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' {
- backslash = true
- } else {
- backslash = false
- }
- }
- }
- if !lastSpace {
- cc = append(cc, s[start:])
- }
- if GOOS == "aix" {
- // -Wl,-bnoobjreorder is mandatory to keep the same layout
- // in .text section.
- cc = append(cc, "-Wl,-bnoobjreorder")
- }
- libbase := GOOS + "_" + GOARCH
- if runtime.Compiler == "gccgo" {
- libbase = "gccgo_" + libgodir + "_fPIC"
- } else {
- switch GOOS {
- case "darwin", "ios":
- if GOARCH == "arm64" {
- libbase += "_shared"
- }
- case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos":
- libbase += "_shared"
- }
- }
- libgodir = filepath.Join(GOPATH, "pkg", libbase, "testcarchive")
- cc = append(cc, "-I", libgodir)
- // Force reallocation (and avoid aliasing bugs) for parallel tests that append to cc.
- cc = cc[:len(cc):len(cc)]
- if GOOS == "windows" {
- exeSuffix = ".exe"
- }
- return m.Run()
- }
- func goEnv(key string) string {
- out, err := exec.Command("go", "env", key).Output()
- if err != nil {
- if ee, ok := err.(*exec.ExitError); ok {
- fmt.Fprintf(os.Stderr, "%s", ee.Stderr)
- }
- log.Panicf("go env %s failed:\n%s\n", key, err)
- }
- return strings.TrimSpace(string(out))
- }
- func cmdToRun(name string) []string {
- execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec"
- executor, err := exec.LookPath(execScript)
- if err != nil {
- return []string{name}
- }
- return []string{executor, name}
- }
- // genHeader writes a C header file for the C-exported declarations found in .go
- // source files in dir.
- //
- // TODO(golang.org/issue/35715): This should be simpler.
- func genHeader(t *testing.T, header, dir string) {
- t.Helper()
- // The 'cgo' command generates a number of additional artifacts,
- // but we're only interested in the header.
- // Shunt the rest of the outputs to a temporary directory.
- objDir, err := os.MkdirTemp(GOPATH, "_obj")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(objDir)
- files, err := filepath.Glob(filepath.Join(dir, "*.go"))
- if err != nil {
- t.Fatal(err)
- }
- cmd := exec.Command("go", "tool", "cgo",
- "-objdir", objDir,
- "-exportheader", header)
- cmd.Args = append(cmd.Args, files...)
- t.Log(cmd.Args)
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- }
- func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) {
- t.Helper()
- cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
- t.Log(buildcmd)
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if !testWork {
- defer func() {
- os.Remove(libgoa)
- os.Remove(libgoh)
- }()
- }
- ccArgs := append(cc, "-o", exe, "main.c")
- if GOOS == "windows" {
- ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm")
- } else {
- ccArgs = append(ccArgs, "main_unix.c", libgoa)
- }
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- t.Log(ccArgs)
- if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if !testWork {
- defer os.Remove(exe)
- }
- binArgs := append(cmdToRun(exe), "arg1", "arg2")
- cmd = exec.Command(binArgs[0], binArgs[1:]...)
- if runtime.Compiler == "gccgo" {
- cmd.Env = append(os.Environ(), "GCCGO=1")
- }
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- checkLineComments(t, libgoh)
- }
- var badLineRegexp = regexp.MustCompile(`(?m)^#line [0-9]+ "/.*$`)
- // checkLineComments checks that the export header generated by
- // -buildmode=c-archive doesn't have any absolute paths in the #line
- // comments. We don't want those paths because they are unhelpful for
- // the user and make the files change based on details of the location
- // of GOPATH.
- func checkLineComments(t *testing.T, hdrname string) {
- hdr, err := os.ReadFile(hdrname)
- if err != nil {
- if !os.IsNotExist(err) {
- t.Error(err)
- }
- return
- }
- if line := badLineRegexp.Find(hdr); line != nil {
- t.Errorf("bad #line directive with absolute path in %s: %q", hdrname, line)
- }
- }
- // checkArchive verifies that the created library looks OK.
- // We just check a couple of things now, we can add more checks as needed.
- func checkArchive(t *testing.T, arname string) {
- t.Helper()
- switch GOOS {
- case "aix", "darwin", "ios", "windows":
- // We don't have any checks for non-ELF libraries yet.
- if _, err := os.Stat(arname); err != nil {
- t.Errorf("archive %s does not exist: %v", arname, err)
- }
- default:
- checkELFArchive(t, arname)
- }
- }
- // checkELFArchive checks an ELF archive.
- func checkELFArchive(t *testing.T, arname string) {
- t.Helper()
- f, err := os.Open(arname)
- if err != nil {
- t.Errorf("archive %s does not exist: %v", arname, err)
- return
- }
- defer f.Close()
- // TODO(iant): put these in a shared package? But where?
- const (
- magic = "!<arch>\n"
- fmag = "`\n"
- namelen = 16
- datelen = 12
- uidlen = 6
- gidlen = 6
- modelen = 8
- sizelen = 10
- fmaglen = 2
- hdrlen = namelen + datelen + uidlen + gidlen + modelen + sizelen + fmaglen
- )
- type arhdr struct {
- name string
- date string
- uid string
- gid string
- mode string
- size string
- fmag string
- }
- var magbuf [len(magic)]byte
- if _, err := io.ReadFull(f, magbuf[:]); err != nil {
- t.Errorf("%s: archive too short", arname)
- return
- }
- if string(magbuf[:]) != magic {
- t.Errorf("%s: incorrect archive magic string %q", arname, magbuf)
- }
- off := int64(len(magic))
- for {
- if off&1 != 0 {
- var b [1]byte
- if _, err := f.Read(b[:]); err != nil {
- if err == io.EOF {
- break
- }
- t.Errorf("%s: error skipping alignment byte at %d: %v", arname, off, err)
- }
- off++
- }
- var hdrbuf [hdrlen]byte
- if _, err := io.ReadFull(f, hdrbuf[:]); err != nil {
- if err == io.EOF {
- break
- }
- t.Errorf("%s: error reading archive header at %d: %v", arname, off, err)
- return
- }
- var hdr arhdr
- hdrslice := hdrbuf[:]
- set := func(len int, ps *string) {
- *ps = string(bytes.TrimSpace(hdrslice[:len]))
- hdrslice = hdrslice[len:]
- }
- set(namelen, &hdr.name)
- set(datelen, &hdr.date)
- set(uidlen, &hdr.uid)
- set(gidlen, &hdr.gid)
- set(modelen, &hdr.mode)
- set(sizelen, &hdr.size)
- hdr.fmag = string(hdrslice[:fmaglen])
- hdrslice = hdrslice[fmaglen:]
- if len(hdrslice) != 0 {
- t.Fatalf("internal error: len(hdrslice) == %d", len(hdrslice))
- }
- if hdr.fmag != fmag {
- t.Errorf("%s: invalid fmagic value %q at %d", arname, hdr.fmag, off)
- return
- }
- size, err := strconv.ParseInt(hdr.size, 10, 64)
- if err != nil {
- t.Errorf("%s: error parsing size %q at %d: %v", arname, hdr.size, off, err)
- return
- }
- off += hdrlen
- switch hdr.name {
- case "__.SYMDEF", "/", "/SYM64/":
- // The archive symbol map.
- case "//", "ARFILENAMES/":
- // The extended name table.
- default:
- // This should be an ELF object.
- checkELFArchiveObject(t, arname, off, io.NewSectionReader(f, off, size))
- }
- off += size
- if _, err := f.Seek(off, os.SEEK_SET); err != nil {
- t.Errorf("%s: failed to seek to %d: %v", arname, off, err)
- }
- }
- }
- // checkELFArchiveObject checks an object in an ELF archive.
- func checkELFArchiveObject(t *testing.T, arname string, off int64, obj io.ReaderAt) {
- t.Helper()
- ef, err := elf.NewFile(obj)
- if err != nil {
- t.Errorf("%s: failed to open ELF file at %d: %v", arname, off, err)
- return
- }
- defer ef.Close()
- // Verify section types.
- for _, sec := range ef.Sections {
- want := elf.SHT_NULL
- switch sec.Name {
- case ".text", ".data":
- want = elf.SHT_PROGBITS
- case ".bss":
- want = elf.SHT_NOBITS
- case ".symtab":
- want = elf.SHT_SYMTAB
- case ".strtab":
- want = elf.SHT_STRTAB
- case ".init_array":
- want = elf.SHT_INIT_ARRAY
- case ".fini_array":
- want = elf.SHT_FINI_ARRAY
- case ".preinit_array":
- want = elf.SHT_PREINIT_ARRAY
- }
- if want != elf.SHT_NULL && sec.Type != want {
- t.Errorf("%s: incorrect section type in elf file at %d for section %q: got %v want %v", arname, off, sec.Name, sec.Type, want)
- }
- }
- }
- func TestInstall(t *testing.T) {
- if !testWork {
- defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }
- libgoa := "libgo.a"
- if runtime.Compiler == "gccgo" {
- libgoa = "liblibgo.a"
- }
- // Generate the p.h header file.
- //
- // 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
- // would also attempt to install transitive standard-library dependencies to
- // GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
- // be running this test in a GOROOT owned by root.)
- genHeader(t, "p.h", "./p")
- testInstall(t, "./testp1"+exeSuffix,
- filepath.Join(libgodir, libgoa),
- filepath.Join(libgodir, "libgo.h"),
- "go", "install", "-buildmode=c-archive", "./libgo")
- // Test building libgo other than installing it.
- // Header files are now present.
- testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h",
- "go", "build", "-buildmode=c-archive", filepath.Join(".", "libgo", "libgo.go"))
- testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h",
- "go", "build", "-buildmode=c-archive", "-o", "libgo.a", "./libgo")
- }
- func TestEarlySignalHandler(t *testing.T) {
- switch GOOS {
- case "darwin", "ios":
- switch GOARCH {
- case "arm64":
- t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
- }
- case "windows":
- t.Skip("skipping signal test on Windows")
- }
- if !testWork {
- defer func() {
- os.Remove("libgo2.a")
- os.Remove("libgo2.h")
- os.Remove("testp" + exeSuffix)
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- checkLineComments(t, "libgo2.h")
- checkArchive(t, "libgo2.a")
- ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- darwin := "0"
- if runtime.GOOS == "darwin" {
- darwin = "1"
- }
- cmd = exec.Command(bin[0], append(bin[1:], darwin)...)
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- }
- func TestSignalForwarding(t *testing.T) {
- checkSignalForwardingTest(t)
- buildSignalForwardingTest(t)
- cmd := exec.Command(bin[0], append(bin[1:], "1")...)
- out, err := cmd.CombinedOutput()
- t.Logf("%v\n%s", cmd.Args, out)
- expectSignal(t, err, syscall.SIGSEGV, 0)
- // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
- if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
- // Test SIGPIPE forwarding
- cmd = exec.Command(bin[0], append(bin[1:], "3")...)
- out, err = cmd.CombinedOutput()
- if len(out) > 0 {
- t.Logf("%s", out)
- }
- expectSignal(t, err, syscall.SIGPIPE, 0)
- }
- }
- func TestSignalForwardingExternal(t *testing.T) {
- if GOOS == "freebsd" || GOOS == "aix" {
- t.Skipf("skipping on %s/%s; signal always goes to the Go runtime", GOOS, GOARCH)
- } else if GOOS == "darwin" && GOARCH == "amd64" {
- t.Skipf("skipping on %s/%s: runtime does not permit SI_USER SIGSEGV", GOOS, GOARCH)
- }
- checkSignalForwardingTest(t)
- buildSignalForwardingTest(t)
- // We want to send the process a signal and see if it dies.
- // Normally the signal goes to the C thread, the Go signal
- // handler picks it up, sees that it is running in a C thread,
- // and the program dies. Unfortunately, occasionally the
- // signal is delivered to a Go thread, which winds up
- // discarding it because it was sent by another program and
- // there is no Go handler for it. To avoid this, run the
- // program several times in the hopes that it will eventually
- // fail.
- const tries = 20
- for i := 0; i < tries; i++ {
- err := runSignalForwardingTest(t, "2")
- if err == nil {
- continue
- }
- // If the signal is delivered to a C thread, as expected,
- // the Go signal handler will disable itself and re-raise
- // the signal, causing the program to die with SIGSEGV.
- //
- // It is also possible that the signal will be
- // delivered to a Go thread, such as a GC thread.
- // Currently when the Go runtime sees that a SIGSEGV was
- // sent from a different program, it first tries to send
- // the signal to the os/signal API. If nothing is looking
- // for (or explicitly ignoring) SIGSEGV, then it crashes.
- // Because the Go runtime is invoked via a c-archive,
- // it treats this as GOTRACEBACK=crash, meaning that it
- // dumps a stack trace for all goroutines, which it does
- // by raising SIGQUIT. The effect is that we will see the
- // program die with SIGQUIT in that case, not SIGSEGV.
- if expectSignal(t, err, syscall.SIGSEGV, syscall.SIGQUIT) {
- return
- }
- }
- t.Errorf("program succeeded unexpectedly %d times", tries)
- }
- func TestSignalForwardingGo(t *testing.T) {
- // This test fails on darwin-amd64 because of the special
- // handling of user-generated SIGSEGV signals in fixsigcode in
- // runtime/signal_darwin_amd64.go.
- if runtime.GOOS == "darwin" && runtime.GOARCH == "amd64" {
- t.Skip("not supported on darwin-amd64")
- }
- checkSignalForwardingTest(t)
- buildSignalForwardingTest(t)
- err := runSignalForwardingTest(t, "4")
- // Occasionally the signal will be delivered to a C thread,
- // and the program will crash with SIGSEGV.
- expectSignal(t, err, syscall.SIGQUIT, syscall.SIGSEGV)
- }
- // checkSignalForwardingTest calls t.Skip if the SignalForwarding test
- // doesn't work on this platform.
- func checkSignalForwardingTest(t *testing.T) {
- switch GOOS {
- case "darwin", "ios":
- switch GOARCH {
- case "arm64":
- t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH)
- }
- case "windows":
- t.Skip("skipping signal test on Windows")
- }
- }
- // buildSignalForwardingTest builds the executable used by the various
- // signal forwarding tests.
- func buildSignalForwardingTest(t *testing.T) {
- if !testWork {
- t.Cleanup(func() {
- os.Remove("libgo2.a")
- os.Remove("libgo2.h")
- os.Remove("testp" + exeSuffix)
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- })
- }
- t.Log("go build -buildmode=c-archive -o libgo2.a ./libgo2")
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "./libgo2")
- out, err := cmd.CombinedOutput()
- if len(out) > 0 {
- t.Logf("%s", out)
- }
- if err != nil {
- t.Fatal(err)
- }
- checkLineComments(t, "libgo2.h")
- checkArchive(t, "libgo2.a")
- ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- t.Log(ccArgs)
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- if len(out) > 0 {
- t.Logf("%s", out)
- }
- if err != nil {
- t.Fatal(err)
- }
- }
- func runSignalForwardingTest(t *testing.T, arg string) error {
- t.Logf("%v %s", bin, arg)
- cmd := exec.Command(bin[0], append(bin[1:], arg)...)
- var out strings.Builder
- cmd.Stdout = &out
- stderr, err := cmd.StderrPipe()
- if err != nil {
- t.Fatal(err)
- }
- defer stderr.Close()
- r := bufio.NewReader(stderr)
- err = cmd.Start()
- if err != nil {
- t.Fatal(err)
- }
- // Wait for trigger to ensure that process is started.
- ok, err := r.ReadString('\n')
- // Verify trigger.
- if err != nil || ok != "OK\n" {
- t.Fatal("Did not receive OK signal")
- }
- var wg sync.WaitGroup
- wg.Add(1)
- var errsb strings.Builder
- go func() {
- defer wg.Done()
- io.Copy(&errsb, r)
- }()
- // Give the program a chance to enter the function.
- // If the program doesn't get there the test will still
- // pass, although it doesn't quite test what we intended.
- // This is fine as long as the program normally makes it.
- time.Sleep(time.Millisecond)
- cmd.Process.Signal(syscall.SIGSEGV)
- err = cmd.Wait()
- s := out.String()
- if len(s) > 0 {
- t.Log(s)
- }
- wg.Wait()
- s = errsb.String()
- if len(s) > 0 {
- t.Log(s)
- }
- return err
- }
- // expectSignal checks that err, the exit status of a test program,
- // shows a failure due to a specific signal or two. Returns whether we
- // found an expected signal.
- func expectSignal(t *testing.T, err error, sig1, sig2 syscall.Signal) bool {
- t.Helper()
- if err == nil {
- t.Error("test program succeeded unexpectedly")
- } else if ee, ok := err.(*exec.ExitError); !ok {
- t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err)
- } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
- t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys())
- } else if !ws.Signaled() || (ws.Signal() != sig1 && ws.Signal() != sig2) {
- if sig2 == 0 {
- t.Errorf("got %q; expected signal %q", ee, sig1)
- } else {
- t.Errorf("got %q; expected signal %q or %q", ee, sig1, sig2)
- }
- } else {
- return true
- }
- return false
- }
- func TestOsSignal(t *testing.T) {
- switch GOOS {
- case "windows":
- t.Skip("skipping signal test on Windows")
- }
- if !testWork {
- defer func() {
- os.Remove("libgo3.a")
- os.Remove("libgo3.h")
- os.Remove("testp" + exeSuffix)
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "./libgo3")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- checkLineComments(t, "libgo3.h")
- checkArchive(t, "libgo3.a")
- ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- }
- func TestSigaltstack(t *testing.T) {
- switch GOOS {
- case "windows":
- t.Skip("skipping signal test on Windows")
- }
- if !testWork {
- defer func() {
- os.Remove("libgo4.a")
- os.Remove("libgo4.h")
- os.Remove("testp" + exeSuffix)
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "./libgo4")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- checkLineComments(t, "libgo4.h")
- checkArchive(t, "libgo4.a")
- ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- }
- const testar = `#!/usr/bin/env bash
- while [[ $1 == -* ]] >/dev/null; do
- shift
- done
- echo "testar" > $1
- echo "testar" > PWD/testar.ran
- `
- func TestExtar(t *testing.T) {
- switch GOOS {
- case "windows":
- t.Skip("skipping signal test on Windows")
- }
- if runtime.Compiler == "gccgo" {
- t.Skip("skipping -extar test when using gccgo")
- }
- if runtime.GOOS == "ios" {
- t.Skip("shell scripts are not executable on iOS hosts")
- }
- if !testWork {
- defer func() {
- os.Remove("libgo4.a")
- os.Remove("libgo4.h")
- os.Remove("testar")
- os.Remove("testar.ran")
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }()
- }
- os.Remove("testar")
- dir, err := os.Getwd()
- if err != nil {
- t.Fatal(err)
- }
- s := strings.Replace(testar, "PWD", dir, 1)
- if err := os.WriteFile("testar", []byte(s), 0777); err != nil {
- t.Fatal(err)
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "./libgo4")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- checkLineComments(t, "libgo4.h")
- if _, err := os.Stat("testar.ran"); err != nil {
- if os.IsNotExist(err) {
- t.Error("testar does not exist after go build")
- } else {
- t.Errorf("error checking testar: %v", err)
- }
- }
- }
- func TestPIE(t *testing.T) {
- switch GOOS {
- case "windows", "darwin", "ios", "plan9":
- t.Skipf("skipping PIE test on %s", GOOS)
- }
- if !testWork {
- defer func() {
- os.Remove("testp" + exeSuffix)
- os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }()
- }
- // Generate the p.h header file.
- //
- // 'go install -i -buildmode=c-archive ./libgo' would do that too, but that
- // would also attempt to install transitive standard-library dependencies to
- // GOROOT, and we cannot assume that GOROOT is writable. (A non-root user may
- // be running this test in a GOROOT owned by root.)
- genHeader(t, "p.h", "./p")
- cmd := exec.Command("go", "install", "-buildmode=c-archive", "./libgo")
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- libgoa := "libgo.a"
- if runtime.Compiler == "gccgo" {
- libgoa = "liblibgo.a"
- }
- ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join(libgodir, libgoa))
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- binArgs := append(bin, "arg1", "arg2")
- cmd = exec.Command(binArgs[0], binArgs[1:]...)
- if runtime.Compiler == "gccgo" {
- cmd.Env = append(os.Environ(), "GCCGO=1")
- }
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if GOOS != "aix" {
- f, err := elf.Open("testp" + exeSuffix)
- if err != nil {
- t.Fatal("elf.Open failed: ", err)
- }
- defer f.Close()
- if hasDynTag(t, f, elf.DT_TEXTREL) {
- t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix)
- }
- }
- }
- func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool {
- ds := f.SectionByType(elf.SHT_DYNAMIC)
- if ds == nil {
- t.Error("no SHT_DYNAMIC section")
- return false
- }
- d, err := ds.Data()
- if err != nil {
- t.Errorf("can't read SHT_DYNAMIC contents: %v", err)
- return false
- }
- for len(d) > 0 {
- var t elf.DynTag
- switch f.Class {
- case elf.ELFCLASS32:
- t = elf.DynTag(f.ByteOrder.Uint32(d[:4]))
- d = d[8:]
- case elf.ELFCLASS64:
- t = elf.DynTag(f.ByteOrder.Uint64(d[:8]))
- d = d[16:]
- }
- if t == tag {
- return true
- }
- }
- return false
- }
- func TestSIGPROF(t *testing.T) {
- switch GOOS {
- case "windows", "plan9":
- t.Skipf("skipping SIGPROF test on %s", GOOS)
- case "darwin", "ios":
- t.Skipf("skipping SIGPROF test on %s; see https://golang.org/issue/19320", GOOS)
- }
- t.Parallel()
- if !testWork {
- defer func() {
- os.Remove("testp6" + exeSuffix)
- os.Remove("libgo6.a")
- os.Remove("libgo6.h")
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "./libgo6")
- out, err := cmd.CombinedOutput()
- t.Logf("%v\n%s", cmd.Args, out)
- if err != nil {
- t.Fatal(err)
- }
- checkLineComments(t, "libgo6.h")
- checkArchive(t, "libgo6.a")
- ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- if err != nil {
- t.Fatal(err)
- }
- argv := cmdToRun("./testp6")
- cmd = exec.Command(argv[0], argv[1:]...)
- out, err = cmd.CombinedOutput()
- t.Logf("%v\n%s", argv, out)
- if err != nil {
- t.Fatal(err)
- }
- }
- // TestCompileWithoutShared tests that if we compile code without the
- // -shared option, we can put it into an archive. When we use the go
- // tool with -buildmode=c-archive, it passes -shared to the compiler,
- // so we override that. The go tool doesn't work this way, but Bazel
- // will likely do it in the future. And it ought to work. This test
- // was added because at one time it did not work on PPC Linux.
- func TestCompileWithoutShared(t *testing.T) {
- // For simplicity, reuse the signal forwarding test.
- checkSignalForwardingTest(t)
- if !testWork {
- defer func() {
- os.Remove("libgo2.a")
- os.Remove("libgo2.h")
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-gcflags=-shared=false", "-o", "libgo2.a", "./libgo2")
- out, err := cmd.CombinedOutput()
- t.Logf("%v\n%s", cmd.Args, out)
- if err != nil {
- t.Fatal(err)
- }
- checkLineComments(t, "libgo2.h")
- checkArchive(t, "libgo2.a")
- exe := "./testnoshared" + exeSuffix
- // In some cases, -no-pie is needed here, but not accepted everywhere. First try
- // if -no-pie is accepted. See #22126.
- ccArgs := append(cc, "-o", exe, "-no-pie", "main5.c", "libgo2.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- // If -no-pie unrecognized, try -nopie if this is possibly clang
- if err != nil && bytes.Contains(out, []byte("unknown")) && !strings.Contains(cc[0], "gcc") {
- ccArgs = append(cc, "-o", exe, "-nopie", "main5.c", "libgo2.a")
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- }
- // Don't use either -no-pie or -nopie
- if err != nil && bytes.Contains(out, []byte("unrecognized")) {
- ccArgs = append(cc, "-o", exe, "main5.c", "libgo2.a")
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- }
- if err != nil {
- t.Fatal(err)
- }
- if !testWork {
- defer os.Remove(exe)
- }
- binArgs := append(cmdToRun(exe), "1")
- out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", binArgs, out)
- expectSignal(t, err, syscall.SIGSEGV, 0)
- // SIGPIPE is never forwarded on darwin. See golang.org/issue/33384.
- if runtime.GOOS != "darwin" && runtime.GOOS != "ios" {
- binArgs := append(cmdToRun(exe), "3")
- out, err = exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", binArgs, out)
- expectSignal(t, err, syscall.SIGPIPE, 0)
- }
- }
- // Test that installing a second time recreates the header file.
- func TestCachedInstall(t *testing.T) {
- if !testWork {
- defer os.RemoveAll(filepath.Join(GOPATH, "pkg"))
- }
- h := filepath.Join(libgodir, "libgo.h")
- buildcmd := []string{"go", "install", "-buildmode=c-archive", "./libgo"}
- cmd := exec.Command(buildcmd[0], buildcmd[1:]...)
- t.Log(buildcmd)
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if _, err := os.Stat(h); err != nil {
- t.Errorf("libgo.h not installed: %v", err)
- }
- if err := os.Remove(h); err != nil {
- t.Fatal(err)
- }
- cmd = exec.Command(buildcmd[0], buildcmd[1:]...)
- t.Log(buildcmd)
- if out, err := cmd.CombinedOutput(); err != nil {
- t.Logf("%s", out)
- t.Fatal(err)
- }
- if _, err := os.Stat(h); err != nil {
- t.Errorf("libgo.h not installed in second run: %v", err)
- }
- }
- // Issue 35294.
- func TestManyCalls(t *testing.T) {
- t.Parallel()
- if !testWork {
- defer func() {
- os.Remove("testp7" + exeSuffix)
- os.Remove("libgo7.a")
- os.Remove("libgo7.h")
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo7.a", "./libgo7")
- out, err := cmd.CombinedOutput()
- t.Logf("%v\n%s", cmd.Args, out)
- if err != nil {
- t.Fatal(err)
- }
- checkLineComments(t, "libgo7.h")
- checkArchive(t, "libgo7.a")
- ccArgs := append(cc, "-o", "testp7"+exeSuffix, "main7.c", "libgo7.a")
- if runtime.Compiler == "gccgo" {
- ccArgs = append(ccArgs, "-lgo")
- }
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- if err != nil {
- t.Fatal(err)
- }
- argv := cmdToRun("./testp7")
- cmd = exec.Command(argv[0], argv[1:]...)
- sb := new(strings.Builder)
- cmd.Stdout = sb
- cmd.Stderr = sb
- if err := cmd.Start(); err != nil {
- t.Fatal(err)
- }
- timer := time.AfterFunc(time.Minute,
- func() {
- t.Error("test program timed out")
- cmd.Process.Kill()
- },
- )
- defer timer.Stop()
- err = cmd.Wait()
- t.Logf("%v\n%s", cmd.Args, sb)
- if err != nil {
- t.Error(err)
- }
- }
- // Issue 49288.
- func TestPreemption(t *testing.T) {
- if runtime.Compiler == "gccgo" {
- t.Skip("skipping asynchronous preemption test with gccgo")
- }
- t.Parallel()
- if !testWork {
- defer func() {
- os.Remove("testp8" + exeSuffix)
- os.Remove("libgo8.a")
- os.Remove("libgo8.h")
- }()
- }
- cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo8.a", "./libgo8")
- out, err := cmd.CombinedOutput()
- t.Logf("%v\n%s", cmd.Args, out)
- if err != nil {
- t.Fatal(err)
- }
- checkLineComments(t, "libgo8.h")
- checkArchive(t, "libgo8.a")
- ccArgs := append(cc, "-o", "testp8"+exeSuffix, "main8.c", "libgo8.a")
- out, err = exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput()
- t.Logf("%v\n%s", ccArgs, out)
- if err != nil {
- t.Fatal(err)
- }
- argv := cmdToRun("./testp8")
- cmd = exec.Command(argv[0], argv[1:]...)
- sb := new(strings.Builder)
- cmd.Stdout = sb
- cmd.Stderr = sb
- if err := cmd.Start(); err != nil {
- t.Fatal(err)
- }
- timer := time.AfterFunc(time.Minute,
- func() {
- t.Error("test program timed out")
- cmd.Process.Kill()
- },
- )
- defer timer.Stop()
- err = cmd.Wait()
- t.Logf("%v\n%s", cmd.Args, sb)
- if err != nil {
- t.Error(err)
- }
- }
|