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