Home | History | Annotate | Download | only in testshared
      1 // Copyright 2015 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.
      4 
      5 package shared_test
      6 
      7 import (
      8 	"bufio"
      9 	"bytes"
     10 	"debug/elf"
     11 	"encoding/binary"
     12 	"errors"
     13 	"flag"
     14 	"fmt"
     15 	"go/build"
     16 	"io"
     17 	"io/ioutil"
     18 	"log"
     19 	"math/rand"
     20 	"os"
     21 	"os/exec"
     22 	"path/filepath"
     23 	"regexp"
     24 	"runtime"
     25 	"strings"
     26 	"testing"
     27 	"time"
     28 )
     29 
     30 var gopathInstallDir, gorootInstallDir, suffix string
     31 
     32 // This is the smallest set of packages we can link into a shared
     33 // library (runtime/cgo is built implicitly).
     34 var minpkgs = []string{"runtime", "sync/atomic"}
     35 var soname = "libruntime,sync-atomic.so"
     36 
     37 // run runs a command and calls t.Errorf if it fails.
     38 func run(t *testing.T, msg string, args ...string) {
     39 	c := exec.Command(args[0], args[1:]...)
     40 	if output, err := c.CombinedOutput(); err != nil {
     41 		t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output)
     42 	}
     43 }
     44 
     45 // goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
     46 // t.Fatalf if the command fails.
     47 func goCmd(t *testing.T, args ...string) {
     48 	newargs := []string{args[0], "-installsuffix=" + suffix}
     49 	if testing.Verbose() {
     50 		newargs = append(newargs, "-x")
     51 	}
     52 	newargs = append(newargs, args[1:]...)
     53 	c := exec.Command("go", newargs...)
     54 	var output []byte
     55 	var err error
     56 	if testing.Verbose() {
     57 		fmt.Printf("+ go %s\n", strings.Join(newargs, " "))
     58 		c.Stdout = os.Stdout
     59 		c.Stderr = os.Stderr
     60 		err = c.Run()
     61 		output = []byte("(output above)")
     62 	} else {
     63 		output, err = c.CombinedOutput()
     64 	}
     65 	if err != nil {
     66 		if t != nil {
     67 			t.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
     68 		} else {
     69 			log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
     70 		}
     71 	}
     72 }
     73 
     74 // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
     75 func testMain(m *testing.M) (int, error) {
     76 	// Because go install -buildmode=shared $standard_library_package always
     77 	// installs into $GOROOT, here are some gymnastics to come up with a unique
     78 	// installsuffix to use in this test that we can clean up afterwards.
     79 	myContext := build.Default
     80 	runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
     81 	if err != nil {
     82 		return 0, fmt.Errorf("import failed: %v", err)
     83 	}
     84 	for i := 0; i < 10000; i++ {
     85 		try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63())
     86 		err = os.Mkdir(try, 0700)
     87 		if os.IsExist(err) {
     88 			continue
     89 		}
     90 		if err == nil {
     91 			gorootInstallDir = try
     92 		}
     93 		break
     94 	}
     95 	if err != nil {
     96 		return 0, fmt.Errorf("can't create temporary directory: %v", err)
     97 	}
     98 	if gorootInstallDir == "" {
     99 		return 0, errors.New("could not create temporary directory after 10000 tries")
    100 	}
    101 	if testing.Verbose() {
    102 		fmt.Printf("+ mkdir -p %s\n", gorootInstallDir)
    103 	}
    104 	defer os.RemoveAll(gorootInstallDir)
    105 
    106 	// Some tests need to edit the source in GOPATH, so copy this directory to a
    107 	// temporary directory and chdir to that.
    108 	scratchDir, err := ioutil.TempDir("", "testshared")
    109 	if err != nil {
    110 		return 0, fmt.Errorf("TempDir failed: %v", err)
    111 	}
    112 	if testing.Verbose() {
    113 		fmt.Printf("+ mkdir -p %s\n", scratchDir)
    114 	}
    115 	defer os.RemoveAll(scratchDir)
    116 	err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
    117 		scratchPath := filepath.Join(scratchDir, path)
    118 		if info.IsDir() {
    119 			if path == "." {
    120 				return nil
    121 			}
    122 			if testing.Verbose() {
    123 				fmt.Printf("+ mkdir -p %s\n", scratchPath)
    124 			}
    125 			return os.Mkdir(scratchPath, info.Mode())
    126 		} else {
    127 			fromBytes, err := ioutil.ReadFile(path)
    128 			if err != nil {
    129 				return err
    130 			}
    131 			if testing.Verbose() {
    132 				fmt.Printf("+ cp %s %s\n", path, scratchPath)
    133 			}
    134 			return ioutil.WriteFile(scratchPath, fromBytes, info.Mode())
    135 		}
    136 	})
    137 	if err != nil {
    138 		return 0, fmt.Errorf("walk failed: %v", err)
    139 	}
    140 	os.Setenv("GOPATH", scratchDir)
    141 	if testing.Verbose() {
    142 		fmt.Printf("+ export GOPATH=%s\n", scratchDir)
    143 	}
    144 	myContext.GOPATH = scratchDir
    145 	if testing.Verbose() {
    146 		fmt.Printf("+ cd %s\n", scratchDir)
    147 	}
    148 	os.Chdir(scratchDir)
    149 
    150 	// All tests depend on runtime being built into a shared library. Because
    151 	// that takes a few seconds, do it here and have all tests use the version
    152 	// built here.
    153 	suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
    154 	goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
    155 
    156 	myContext.InstallSuffix = suffix + "_dynlink"
    157 	depP, err := myContext.Import("depBase", ".", build.ImportComment)
    158 	if err != nil {
    159 		return 0, fmt.Errorf("import failed: %v", err)
    160 	}
    161 	gopathInstallDir = depP.PkgTargetRoot
    162 	return m.Run(), nil
    163 }
    164 
    165 func TestMain(m *testing.M) {
    166 	flag.Parse()
    167 
    168 	// Some of the tests install binaries into a custom GOPATH.
    169 	// That won't work if GOBIN is set.
    170 	os.Unsetenv("GOBIN")
    171 
    172 	exitCode, err := testMain(m)
    173 	if err != nil {
    174 		log.Fatal(err)
    175 	}
    176 	os.Exit(exitCode)
    177 }
    178 
    179 // The shared library was built at the expected location.
    180 func TestSOBuilt(t *testing.T) {
    181 	_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
    182 	if err != nil {
    183 		t.Error(err)
    184 	}
    185 }
    186 
    187 func hasDynTag(f *elf.File, tag elf.DynTag) bool {
    188 	ds := f.SectionByType(elf.SHT_DYNAMIC)
    189 	if ds == nil {
    190 		return false
    191 	}
    192 	d, err := ds.Data()
    193 	if err != nil {
    194 		return false
    195 	}
    196 	for len(d) > 0 {
    197 		var t elf.DynTag
    198 		switch f.Class {
    199 		case elf.ELFCLASS32:
    200 			t = elf.DynTag(f.ByteOrder.Uint32(d[0:4]))
    201 			d = d[8:]
    202 		case elf.ELFCLASS64:
    203 			t = elf.DynTag(f.ByteOrder.Uint64(d[0:8]))
    204 			d = d[16:]
    205 		}
    206 		if t == tag {
    207 			return true
    208 		}
    209 	}
    210 	return false
    211 }
    212 
    213 // The shared library does not have relocations against the text segment.
    214 func TestNoTextrel(t *testing.T) {
    215 	sopath := filepath.Join(gorootInstallDir, soname)
    216 	f, err := elf.Open(sopath)
    217 	if err != nil {
    218 		t.Fatal("elf.Open failed: ", err)
    219 	}
    220 	defer f.Close()
    221 	if hasDynTag(f, elf.DT_TEXTREL) {
    222 		t.Errorf("%s has DT_TEXTREL set", soname)
    223 	}
    224 }
    225 
    226 // The shared library does not contain symbols called ".dup"
    227 func TestNoDupSymbols(t *testing.T) {
    228 	sopath := filepath.Join(gorootInstallDir, soname)
    229 	f, err := elf.Open(sopath)
    230 	if err != nil {
    231 		t.Fatal("elf.Open failed: ", err)
    232 	}
    233 	defer f.Close()
    234 	syms, err := f.Symbols()
    235 	if err != nil {
    236 		t.Errorf("error reading symbols %v", err)
    237 		return
    238 	}
    239 	for _, s := range syms {
    240 		if s.Name == ".dup" {
    241 			t.Fatalf("%s contains symbol called .dup", sopath)
    242 		}
    243 	}
    244 }
    245 
    246 // The install command should have created a "shlibname" file for the
    247 // listed packages (and runtime/cgo, and math on arm) indicating the
    248 // name of the shared library containing it.
    249 func TestShlibnameFiles(t *testing.T) {
    250 	pkgs := append([]string{}, minpkgs...)
    251 	pkgs = append(pkgs, "runtime/cgo")
    252 	if runtime.GOARCH == "arm" {
    253 		pkgs = append(pkgs, "math")
    254 	}
    255 	for _, pkg := range pkgs {
    256 		shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname")
    257 		contentsb, err := ioutil.ReadFile(shlibnamefile)
    258 		if err != nil {
    259 			t.Errorf("error reading shlibnamefile for %s: %v", pkg, err)
    260 			continue
    261 		}
    262 		contents := strings.TrimSpace(string(contentsb))
    263 		if contents != soname {
    264 			t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents)
    265 		}
    266 	}
    267 }
    268 
    269 // Is a given offset into the file contained in a loaded segment?
    270 func isOffsetLoaded(f *elf.File, offset uint64) bool {
    271 	for _, prog := range f.Progs {
    272 		if prog.Type == elf.PT_LOAD {
    273 			if prog.Off <= offset && offset < prog.Off+prog.Filesz {
    274 				return true
    275 			}
    276 		}
    277 	}
    278 	return false
    279 }
    280 
    281 func rnd(v int32, r int32) int32 {
    282 	if r <= 0 {
    283 		return v
    284 	}
    285 	v += r - 1
    286 	c := v % r
    287 	if c < 0 {
    288 		c += r
    289 	}
    290 	v -= c
    291 	return v
    292 }
    293 
    294 func readwithpad(r io.Reader, sz int32) ([]byte, error) {
    295 	data := make([]byte, rnd(sz, 4))
    296 	_, err := io.ReadFull(r, data)
    297 	if err != nil {
    298 		return nil, err
    299 	}
    300 	data = data[:sz]
    301 	return data, nil
    302 }
    303 
    304 type note struct {
    305 	name    string
    306 	tag     int32
    307 	desc    string
    308 	section *elf.Section
    309 }
    310 
    311 // Read all notes from f. As ELF section names are not supposed to be special, one
    312 // looks for a particular note by scanning all SHT_NOTE sections looking for a note
    313 // with a particular "name" and "tag".
    314 func readNotes(f *elf.File) ([]*note, error) {
    315 	var notes []*note
    316 	for _, sect := range f.Sections {
    317 		if sect.Type != elf.SHT_NOTE {
    318 			continue
    319 		}
    320 		r := sect.Open()
    321 		for {
    322 			var namesize, descsize, tag int32
    323 			err := binary.Read(r, f.ByteOrder, &namesize)
    324 			if err != nil {
    325 				if err == io.EOF {
    326 					break
    327 				}
    328 				return nil, fmt.Errorf("read namesize failed: %v", err)
    329 			}
    330 			err = binary.Read(r, f.ByteOrder, &descsize)
    331 			if err != nil {
    332 				return nil, fmt.Errorf("read descsize failed: %v", err)
    333 			}
    334 			err = binary.Read(r, f.ByteOrder, &tag)
    335 			if err != nil {
    336 				return nil, fmt.Errorf("read type failed: %v", err)
    337 			}
    338 			name, err := readwithpad(r, namesize)
    339 			if err != nil {
    340 				return nil, fmt.Errorf("read name failed: %v", err)
    341 			}
    342 			desc, err := readwithpad(r, descsize)
    343 			if err != nil {
    344 				return nil, fmt.Errorf("read desc failed: %v", err)
    345 			}
    346 			notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
    347 		}
    348 	}
    349 	return notes, nil
    350 }
    351 
    352 func dynStrings(t *testing.T, path string, flag elf.DynTag) []string {
    353 	f, err := elf.Open(path)
    354 	if err != nil {
    355 		t.Fatalf("elf.Open(%q) failed: %v", path, err)
    356 	}
    357 	defer f.Close()
    358 	dynstrings, err := f.DynString(flag)
    359 	if err != nil {
    360 		t.Fatalf("DynString(%s) failed on %s: %v", flag, path, err)
    361 	}
    362 	return dynstrings
    363 }
    364 
    365 func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
    366 	for _, dynstring := range dynStrings(t, path, elf.DT_NEEDED) {
    367 		if re.MatchString(dynstring) {
    368 			return
    369 		}
    370 	}
    371 	t.Errorf("%s is not linked to anything matching %v", path, re)
    372 }
    373 
    374 func AssertIsLinkedTo(t *testing.T, path, lib string) {
    375 	AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
    376 }
    377 
    378 func AssertHasRPath(t *testing.T, path, dir string) {
    379 	for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} {
    380 		for _, dynstring := range dynStrings(t, path, tag) {
    381 			for _, rpath := range strings.Split(dynstring, ":") {
    382 				if filepath.Clean(rpath) == filepath.Clean(dir) {
    383 					return
    384 				}
    385 			}
    386 		}
    387 	}
    388 	t.Errorf("%s does not have rpath %s", path, dir)
    389 }
    390 
    391 // Build a trivial program that links against the shared runtime and check it runs.
    392 func TestTrivialExecutable(t *testing.T) {
    393 	goCmd(t, "install", "-linkshared", "trivial")
    394 	run(t, "trivial executable", "./bin/trivial")
    395 	AssertIsLinkedTo(t, "./bin/trivial", soname)
    396 	AssertHasRPath(t, "./bin/trivial", gorootInstallDir)
    397 }
    398 
    399 // Build a trivial program in PIE mode that links against the shared runtime and check it runs.
    400 func TestTrivialExecutablePIE(t *testing.T) {
    401 	goCmd(t, "build", "-buildmode=pie", "-o", "trivial.pie", "-linkshared", "trivial")
    402 	run(t, "trivial executable", "./trivial.pie")
    403 	AssertIsLinkedTo(t, "./trivial.pie", soname)
    404 	AssertHasRPath(t, "./trivial.pie", gorootInstallDir)
    405 }
    406 
    407 // Build a division test program and check it runs.
    408 func TestDivisionExecutable(t *testing.T) {
    409 	goCmd(t, "install", "-linkshared", "division")
    410 	run(t, "division executable", "./bin/division")
    411 }
    412 
    413 // Build an executable that uses cgo linked against the shared runtime and check it
    414 // runs.
    415 func TestCgoExecutable(t *testing.T) {
    416 	goCmd(t, "install", "-linkshared", "execgo")
    417 	run(t, "cgo executable", "./bin/execgo")
    418 }
    419 
    420 func checkPIE(t *testing.T, name string) {
    421 	f, err := elf.Open(name)
    422 	if err != nil {
    423 		t.Fatal("elf.Open failed: ", err)
    424 	}
    425 	defer f.Close()
    426 	if f.Type != elf.ET_DYN {
    427 		t.Errorf("%s has type %v, want ET_DYN", name, f.Type)
    428 	}
    429 	if hasDynTag(f, elf.DT_TEXTREL) {
    430 		t.Errorf("%s has DT_TEXTREL set", name)
    431 	}
    432 }
    433 
    434 func TestTrivialPIE(t *testing.T) {
    435 	name := "trivial_pie"
    436 	goCmd(t, "build", "-buildmode=pie", "-o="+name, "trivial")
    437 	defer os.Remove(name)
    438 	run(t, name, "./"+name)
    439 	checkPIE(t, name)
    440 }
    441 
    442 func TestCgoPIE(t *testing.T) {
    443 	name := "cgo_pie"
    444 	goCmd(t, "build", "-buildmode=pie", "-o="+name, "execgo")
    445 	defer os.Remove(name)
    446 	run(t, name, "./"+name)
    447 	checkPIE(t, name)
    448 }
    449 
    450 // Build a GOPATH package into a shared library that links against the goroot runtime
    451 // and an executable that links against both.
    452 func TestGopathShlib(t *testing.T) {
    453 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    454 	AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdepBase.so"), soname)
    455 	goCmd(t, "install", "-linkshared", "exe")
    456 	AssertIsLinkedTo(t, "./bin/exe", soname)
    457 	AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so")
    458 	AssertHasRPath(t, "./bin/exe", gorootInstallDir)
    459 	AssertHasRPath(t, "./bin/exe", gopathInstallDir)
    460 	// And check it runs.
    461 	run(t, "executable linked to GOPATH library", "./bin/exe")
    462 }
    463 
    464 // The shared library contains a note listing the packages it contains in a section
    465 // that is not mapped into memory.
    466 func testPkgListNote(t *testing.T, f *elf.File, note *note) {
    467 	if note.section.Flags != 0 {
    468 		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
    469 	}
    470 	if isOffsetLoaded(f, note.section.Offset) {
    471 		t.Errorf("package list section contained in PT_LOAD segment")
    472 	}
    473 	if note.desc != "depBase\n" {
    474 		t.Errorf("incorrect package list %q, want %q", note.desc, "depBase\n")
    475 	}
    476 }
    477 
    478 // The shared library contains a note containing the ABI hash that is mapped into
    479 // memory and there is a local symbol called go.link.abihashbytes that points 16
    480 // bytes into it.
    481 func testABIHashNote(t *testing.T, f *elf.File, note *note) {
    482 	if note.section.Flags != elf.SHF_ALLOC {
    483 		t.Errorf("abi hash section has flags %v, want SHF_ALLOC", note.section.Flags)
    484 	}
    485 	if !isOffsetLoaded(f, note.section.Offset) {
    486 		t.Errorf("abihash section not contained in PT_LOAD segment")
    487 	}
    488 	var hashbytes elf.Symbol
    489 	symbols, err := f.Symbols()
    490 	if err != nil {
    491 		t.Errorf("error reading symbols %v", err)
    492 		return
    493 	}
    494 	for _, sym := range symbols {
    495 		if sym.Name == "go.link.abihashbytes" {
    496 			hashbytes = sym
    497 		}
    498 	}
    499 	if hashbytes.Name == "" {
    500 		t.Errorf("no symbol called go.link.abihashbytes")
    501 		return
    502 	}
    503 	if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
    504 		t.Errorf("%s has incorrect binding %v, want STB_LOCAL", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
    505 	}
    506 	if f.Sections[hashbytes.Section] != note.section {
    507 		t.Errorf("%s has incorrect section %v, want %s", hashbytes.Name, f.Sections[hashbytes.Section].Name, note.section.Name)
    508 	}
    509 	if hashbytes.Value-note.section.Addr != 16 {
    510 		t.Errorf("%s has incorrect offset into section %d, want 16", hashbytes.Name, hashbytes.Value-note.section.Addr)
    511 	}
    512 }
    513 
    514 // A Go shared library contains a note indicating which other Go shared libraries it
    515 // was linked against in an unmapped section.
    516 func testDepsNote(t *testing.T, f *elf.File, note *note) {
    517 	if note.section.Flags != 0 {
    518 		t.Errorf("package list section has flags %v, want 0", note.section.Flags)
    519 	}
    520 	if isOffsetLoaded(f, note.section.Offset) {
    521 		t.Errorf("package list section contained in PT_LOAD segment")
    522 	}
    523 	// libdepBase.so just links against the lib containing the runtime.
    524 	if note.desc != soname {
    525 		t.Errorf("incorrect dependency list %q, want %q", note.desc, soname)
    526 	}
    527 }
    528 
    529 // The shared library contains notes with defined contents; see above.
    530 func TestNotes(t *testing.T) {
    531 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    532 	f, err := elf.Open(filepath.Join(gopathInstallDir, "libdepBase.so"))
    533 	if err != nil {
    534 		t.Fatal(err)
    535 	}
    536 	defer f.Close()
    537 	notes, err := readNotes(f)
    538 	if err != nil {
    539 		t.Fatal(err)
    540 	}
    541 	pkgListNoteFound := false
    542 	abiHashNoteFound := false
    543 	depsNoteFound := false
    544 	for _, note := range notes {
    545 		if note.name != "Go\x00\x00" {
    546 			continue
    547 		}
    548 		switch note.tag {
    549 		case 1: // ELF_NOTE_GOPKGLIST_TAG
    550 			if pkgListNoteFound {
    551 				t.Error("multiple package list notes")
    552 			}
    553 			testPkgListNote(t, f, note)
    554 			pkgListNoteFound = true
    555 		case 2: // ELF_NOTE_GOABIHASH_TAG
    556 			if abiHashNoteFound {
    557 				t.Error("multiple abi hash notes")
    558 			}
    559 			testABIHashNote(t, f, note)
    560 			abiHashNoteFound = true
    561 		case 3: // ELF_NOTE_GODEPS_TAG
    562 			if depsNoteFound {
    563 				t.Error("multiple depedency list notes")
    564 			}
    565 			testDepsNote(t, f, note)
    566 			depsNoteFound = true
    567 		}
    568 	}
    569 	if !pkgListNoteFound {
    570 		t.Error("package list note not found")
    571 	}
    572 	if !abiHashNoteFound {
    573 		t.Error("abi hash note not found")
    574 	}
    575 	if !depsNoteFound {
    576 		t.Error("deps note not found")
    577 	}
    578 }
    579 
    580 // Build a GOPATH package (depBase) into a shared library that links against the goroot
    581 // runtime, another package (dep2) that links against the first, and and an
    582 // executable that links against dep2.
    583 func TestTwoGopathShlibs(t *testing.T) {
    584 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    585 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
    586 	goCmd(t, "install", "-linkshared", "exe2")
    587 	run(t, "executable linked to GOPATH library", "./bin/exe2")
    588 }
    589 
    590 func TestThreeGopathShlibs(t *testing.T) {
    591 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    592 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
    593 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep3")
    594 	goCmd(t, "install", "-linkshared", "exe3")
    595 	run(t, "executable linked to GOPATH library", "./bin/exe3")
    596 }
    597 
    598 // If gccgo is not available or not new enough call t.Skip. Otherwise,
    599 // return a build.Context that is set up for gccgo.
    600 func prepGccgo(t *testing.T) build.Context {
    601 	gccgoName := os.Getenv("GCCGO")
    602 	if gccgoName == "" {
    603 		gccgoName = "gccgo"
    604 	}
    605 	gccgoPath, err := exec.LookPath(gccgoName)
    606 	if err != nil {
    607 		t.Skip("gccgo not found")
    608 	}
    609 	cmd := exec.Command(gccgoPath, "-dumpversion")
    610 	output, err := cmd.CombinedOutput()
    611 	if err != nil {
    612 		t.Fatalf("%s -dumpversion failed: %v\n%s", gccgoPath, err, output)
    613 	}
    614 	if string(output) < "5" {
    615 		t.Skipf("gccgo too old (%s)", strings.TrimSpace(string(output)))
    616 	}
    617 	gccgoContext := build.Default
    618 	gccgoContext.InstallSuffix = suffix + "_fPIC"
    619 	gccgoContext.Compiler = "gccgo"
    620 	gccgoContext.GOPATH = os.Getenv("GOPATH")
    621 	return gccgoContext
    622 }
    623 
    624 // Build a GOPATH package into a shared library with gccgo and an executable that
    625 // links against it.
    626 func TestGoPathShlibGccgo(t *testing.T) {
    627 	gccgoContext := prepGccgo(t)
    628 
    629 	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
    630 
    631 	depP, err := gccgoContext.Import("depBase", ".", build.ImportComment)
    632 	if err != nil {
    633 		t.Fatalf("import failed: %v", err)
    634 	}
    635 	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
    636 	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase")
    637 	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE)
    638 	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe")
    639 	AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE)
    640 	AssertIsLinkedTo(t, "./bin/exe", "libdepBase.so")
    641 	AssertHasRPath(t, "./bin/exe", gccgoInstallDir)
    642 	// And check it runs.
    643 	run(t, "gccgo-built", "./bin/exe")
    644 }
    645 
    646 // The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
    647 // library with gccgo, another GOPATH package that depends on the first and an
    648 // executable that links the second library.
    649 func TestTwoGopathShlibsGccgo(t *testing.T) {
    650 	gccgoContext := prepGccgo(t)
    651 
    652 	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
    653 
    654 	depP, err := gccgoContext.Import("depBase", ".", build.ImportComment)
    655 	if err != nil {
    656 		t.Fatalf("import failed: %v", err)
    657 	}
    658 	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
    659 	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "depBase")
    660 	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2")
    661 	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2")
    662 
    663 	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdepBase.so"), libgoRE)
    664 	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE)
    665 	AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdepBase.so")
    666 	AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE)
    667 	AssertIsLinkedTo(t, "./bin/exe2", "libdep2")
    668 	AssertIsLinkedTo(t, "./bin/exe2", "libdepBase.so")
    669 
    670 	// And check it runs.
    671 	run(t, "gccgo-built", "./bin/exe2")
    672 }
    673 
    674 // Testing rebuilding of shared libraries when they are stale is a bit more
    675 // complicated that it seems like it should be. First, we make everything "old": but
    676 // only a few seconds old, or it might be older than gc (or the runtime source) and
    677 // everything will get rebuilt. Then define a timestamp slightly newer than this
    678 // time, which is what we set the mtime to of a file to cause it to be seen as new,
    679 // and finally another slightly even newer one that we can compare files against to
    680 // see if they have been rebuilt.
    681 var oldTime = time.Now().Add(-9 * time.Second)
    682 var nearlyNew = time.Now().Add(-6 * time.Second)
    683 var stampTime = time.Now().Add(-3 * time.Second)
    684 
    685 // resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the
    686 // test-specific parts of GOROOT) appear old.
    687 func resetFileStamps() {
    688 	chtime := func(path string, info os.FileInfo, err error) error {
    689 		return os.Chtimes(path, oldTime, oldTime)
    690 	}
    691 	reset := func(path string) {
    692 		if err := filepath.Walk(path, chtime); err != nil {
    693 			log.Fatalf("resetFileStamps failed: %v", err)
    694 		}
    695 
    696 	}
    697 	reset("bin")
    698 	reset("pkg")
    699 	reset("src")
    700 	reset(gorootInstallDir)
    701 }
    702 
    703 // touch changes path and returns a function that changes it back.
    704 // It also sets the time of the file, so that we can see if it is rewritten.
    705 func touch(t *testing.T, path string) (cleanup func()) {
    706 	data, err := ioutil.ReadFile(path)
    707 	if err != nil {
    708 		t.Fatal(err)
    709 	}
    710 	old := make([]byte, len(data))
    711 	copy(old, data)
    712 	if bytes.HasPrefix(data, []byte("!<arch>\n")) {
    713 		// Change last digit of build ID.
    714 		// (Content ID in the new content-based build IDs.)
    715 		const marker = `build id "`
    716 		i := bytes.Index(data, []byte(marker))
    717 		if i < 0 {
    718 			t.Fatal("cannot find build id in archive")
    719 		}
    720 		j := bytes.IndexByte(data[i+len(marker):], '"')
    721 		if j < 0 {
    722 			t.Fatal("cannot find build id in archive")
    723 		}
    724 		i += len(marker) + j - 1
    725 		if data[i] == 'a' {
    726 			data[i] = 'b'
    727 		} else {
    728 			data[i] = 'a'
    729 		}
    730 	} else {
    731 		// assume it's a text file
    732 		data = append(data, '\n')
    733 	}
    734 	if err := ioutil.WriteFile(path, data, 0666); err != nil {
    735 		t.Fatal(err)
    736 	}
    737 	if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
    738 		t.Fatal(err)
    739 	}
    740 	return func() {
    741 		if err := ioutil.WriteFile(path, old, 0666); err != nil {
    742 			t.Fatal(err)
    743 		}
    744 	}
    745 }
    746 
    747 // isNew returns if the path is newer than the time stamp used by touch.
    748 func isNew(t *testing.T, path string) bool {
    749 	fi, err := os.Stat(path)
    750 	if err != nil {
    751 		t.Fatal(err)
    752 	}
    753 	return fi.ModTime().After(stampTime)
    754 }
    755 
    756 // Fail unless path has been rebuilt (i.e. is newer than the time stamp used by
    757 // isNew)
    758 func AssertRebuilt(t *testing.T, msg, path string) {
    759 	t.Helper()
    760 	if !isNew(t, path) {
    761 		t.Errorf("%s was not rebuilt (%s)", msg, path)
    762 	}
    763 }
    764 
    765 // Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew)
    766 func AssertNotRebuilt(t *testing.T, msg, path string) {
    767 	t.Helper()
    768 	if isNew(t, path) {
    769 		t.Errorf("%s was rebuilt (%s)", msg, path)
    770 	}
    771 }
    772 
    773 func TestRebuilding(t *testing.T) {
    774 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    775 	goCmd(t, "install", "-linkshared", "exe")
    776 
    777 	// If the source is newer than both the .a file and the .so, both are rebuilt.
    778 	t.Run("newsource", func(t *testing.T) {
    779 		resetFileStamps()
    780 		cleanup := touch(t, "src/depBase/dep.go")
    781 		defer func() {
    782 			cleanup()
    783 			goCmd(t, "install", "-linkshared", "exe")
    784 		}()
    785 		goCmd(t, "install", "-linkshared", "exe")
    786 		AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "depBase.a"))
    787 		AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "libdepBase.so"))
    788 	})
    789 
    790 	// If the .a file is newer than the .so, the .so is rebuilt (but not the .a)
    791 	t.Run("newarchive", func(t *testing.T) {
    792 		resetFileStamps()
    793 		goCmd(t, "list", "-linkshared", "-f={{.ImportPath}} {{.Stale}} {{.StaleReason}} {{.Target}}", "depBase")
    794 		AssertNotRebuilt(t, "new .a file before build", filepath.Join(gopathInstallDir, "depBase.a"))
    795 		cleanup := touch(t, filepath.Join(gopathInstallDir, "depBase.a"))
    796 		defer func() {
    797 			cleanup()
    798 			goCmd(t, "install", "-v", "-linkshared", "exe")
    799 		}()
    800 		goCmd(t, "install", "-v", "-linkshared", "exe")
    801 		AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "depBase.a"))
    802 		AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdepBase.so"))
    803 	})
    804 }
    805 
    806 func appendFile(t *testing.T, path, content string) {
    807 	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
    808 	if err != nil {
    809 		t.Fatalf("os.OpenFile failed: %v", err)
    810 	}
    811 	defer func() {
    812 		err := f.Close()
    813 		if err != nil {
    814 			t.Fatalf("f.Close failed: %v", err)
    815 		}
    816 	}()
    817 	_, err = f.WriteString(content)
    818 	if err != nil {
    819 		t.Fatalf("f.WriteString failed: %v", err)
    820 	}
    821 }
    822 
    823 func writeFile(t *testing.T, path, content string) {
    824 	err := ioutil.WriteFile(path, []byte(content), 0644)
    825 	if err != nil {
    826 		t.Fatalf("ioutil.WriteFile failed: %v", err)
    827 	}
    828 }
    829 
    830 func TestABIChecking(t *testing.T) {
    831 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    832 	goCmd(t, "install", "-linkshared", "exe")
    833 
    834 	// If we make an ABI-breaking change to depBase and rebuild libp.so but not exe,
    835 	// exe will abort with a complaint on startup.
    836 	// This assumes adding an exported function breaks ABI, which is not true in
    837 	// some senses but suffices for the narrow definition of ABI compatibility the
    838 	// toolchain uses today.
    839 	resetFileStamps()
    840 	appendFile(t, "src/depBase/dep.go", "func ABIBreak() {}\n")
    841 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    842 	c := exec.Command("./bin/exe")
    843 	output, err := c.CombinedOutput()
    844 	if err == nil {
    845 		t.Fatal("executing exe did not fail after ABI break")
    846 	}
    847 	scanner := bufio.NewScanner(bytes.NewReader(output))
    848 	foundMsg := false
    849 	const wantLine = "abi mismatch detected between the executable and libdepBase.so"
    850 	for scanner.Scan() {
    851 		if scanner.Text() == wantLine {
    852 			foundMsg = true
    853 			break
    854 		}
    855 	}
    856 	if err = scanner.Err(); err != nil {
    857 		t.Errorf("scanner encountered error: %v", err)
    858 	}
    859 	if !foundMsg {
    860 		t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output)
    861 	}
    862 
    863 	// Rebuilding exe makes it work again.
    864 	goCmd(t, "install", "-linkshared", "exe")
    865 	run(t, "rebuilt exe", "./bin/exe")
    866 
    867 	// If we make a change which does not break ABI (such as adding an unexported
    868 	// function) and rebuild libdepBase.so, exe still works, even if new function
    869 	// is in a file by itself.
    870 	resetFileStamps()
    871 	writeFile(t, "src/depBase/dep2.go", "package depBase\nfunc noABIBreak() {}\n")
    872 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "depBase")
    873 	run(t, "after non-ABI breaking change", "./bin/exe")
    874 }
    875 
    876 // If a package 'explicit' imports a package 'implicit', building
    877 // 'explicit' into a shared library implicitly includes implicit in
    878 // the shared library. Building an executable that imports both
    879 // explicit and implicit builds the code from implicit into the
    880 // executable rather than fetching it from the shared library. The
    881 // link still succeeds and the executable still runs though.
    882 func TestImplicitInclusion(t *testing.T) {
    883 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "explicit")
    884 	goCmd(t, "install", "-linkshared", "implicitcmd")
    885 	run(t, "running executable linked against library that contains same package as it", "./bin/implicitcmd")
    886 }
    887 
    888 // Tests to make sure that the type fields of empty interfaces and itab
    889 // fields of nonempty interfaces are unique even across modules,
    890 // so that interface equality works correctly.
    891 func TestInterface(t *testing.T) {
    892 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_a")
    893 	// Note: iface_i gets installed implicitly as a dependency of iface_a.
    894 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "iface_b")
    895 	goCmd(t, "install", "-linkshared", "iface")
    896 	run(t, "running type/itab uniqueness tester", "./bin/iface")
    897 }
    898 
    899 // Access a global variable from a library.
    900 func TestGlobal(t *testing.T) {
    901 	goCmd(t, "install", "-buildmode=shared", "-linkshared", "globallib")
    902 	goCmd(t, "install", "-linkshared", "global")
    903 	run(t, "global executable", "./bin/global")
    904 	AssertIsLinkedTo(t, "./bin/global", soname)
    905 	AssertHasRPath(t, "./bin/global", gorootInstallDir)
    906 }
    907