github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/misc/cgo/testshared/shared_test.go (about)

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