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