github.com/huandu/go@v0.0.0-20151114150818-04e615e41150/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  	"strings"
    25  	"testing"
    26  	"time"
    27  )
    28  
    29  var gopathInstallDir, gorootInstallDir, suffix string
    30  
    31  // This is the smallest set of packages we can link into a shared
    32  // library (runtime/cgo is built implicitly).
    33  var minpkgs = []string{"runtime", "sync/atomic"}
    34  var soname = "libruntime,sync-atomic.so"
    35  
    36  // run runs a command and calls t.Errorf if it fails.
    37  func run(t *testing.T, msg string, args ...string) {
    38  	c := exec.Command(args[0], args[1:]...)
    39  	if output, err := c.CombinedOutput(); err != nil {
    40  		t.Errorf("executing %s (%s) failed %s:\n%s", strings.Join(args, " "), msg, err, output)
    41  	}
    42  }
    43  
    44  // goCmd invokes the go tool with the installsuffix set up by TestMain. It calls
    45  // t.Errorf if the command fails.
    46  func goCmd(t *testing.T, args ...string) {
    47  	newargs := []string{args[0], "-installsuffix=" + suffix}
    48  	if testing.Verbose() {
    49  		newargs = append(newargs, "-v")
    50  	}
    51  	newargs = append(newargs, args[1:]...)
    52  	c := exec.Command("go", newargs...)
    53  	var output []byte
    54  	var err error
    55  	if testing.Verbose() {
    56  		fmt.Printf("+ go %s\n", strings.Join(newargs, " "))
    57  		c.Stdout = os.Stdout
    58  		c.Stderr = os.Stderr
    59  		err = c.Run()
    60  	} else {
    61  		output, err = c.CombinedOutput()
    62  	}
    63  	if err != nil {
    64  		if t != nil {
    65  			t.Errorf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
    66  		} else {
    67  			log.Fatalf("executing %s failed %v:\n%s", strings.Join(c.Args, " "), err, output)
    68  		}
    69  	}
    70  }
    71  
    72  // TestMain calls testMain so that the latter can use defer (TestMain exits with os.Exit).
    73  func testMain(m *testing.M) (int, error) {
    74  	// Because go install -buildmode=shared $standard_library_package always
    75  	// installs into $GOROOT, here are some gymnastics to come up with a unique
    76  	// installsuffix to use in this test that we can clean up afterwards.
    77  	myContext := build.Default
    78  	runtimeP, err := myContext.Import("runtime", ".", build.ImportComment)
    79  	if err != nil {
    80  		return 0, fmt.Errorf("import failed: %v", err)
    81  	}
    82  	for i := 0; i < 10000; i++ {
    83  		try := fmt.Sprintf("%s_%d_dynlink", runtimeP.PkgTargetRoot, rand.Int63())
    84  		err = os.Mkdir(try, 0700)
    85  		if os.IsExist(err) {
    86  			continue
    87  		}
    88  		if err == nil {
    89  			gorootInstallDir = try
    90  		}
    91  		break
    92  	}
    93  	if err != nil {
    94  		return 0, fmt.Errorf("can't create temporary directory: %v", err)
    95  	}
    96  	if gorootInstallDir == "" {
    97  		return 0, errors.New("could not create temporary directory after 10000 tries")
    98  	}
    99  	defer os.RemoveAll(gorootInstallDir)
   100  
   101  	// Some tests need to edit the source in GOPATH, so copy this directory to a
   102  	// temporary directory and chdir to that.
   103  	scratchDir, err := ioutil.TempDir("", "testshared")
   104  	if err != nil {
   105  		return 0, fmt.Errorf("TempDir failed: %v", err)
   106  	}
   107  	defer os.RemoveAll(scratchDir)
   108  	err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
   109  		scratchPath := filepath.Join(scratchDir, path)
   110  		if info.IsDir() {
   111  			if path == "." {
   112  				return nil
   113  			}
   114  			return os.Mkdir(scratchPath, info.Mode())
   115  		} else {
   116  			fromBytes, err := ioutil.ReadFile(path)
   117  			if err != nil {
   118  				return err
   119  			}
   120  			return ioutil.WriteFile(scratchPath, fromBytes, info.Mode())
   121  		}
   122  	})
   123  	if err != nil {
   124  		return 0, fmt.Errorf("walk failed: %v", err)
   125  	}
   126  	os.Setenv("GOPATH", scratchDir)
   127  	myContext.GOPATH = scratchDir
   128  	os.Chdir(scratchDir)
   129  
   130  	// All tests depend on runtime being built into a shared library. Because
   131  	// that takes a few seconds, do it here and have all tests use the version
   132  	// built here.
   133  	suffix = strings.Split(filepath.Base(gorootInstallDir), "_")[2]
   134  	goCmd(nil, append([]string{"install", "-buildmode=shared"}, minpkgs...)...)
   135  
   136  	myContext.InstallSuffix = suffix + "_dynlink"
   137  	depP, err := myContext.Import("dep", ".", build.ImportComment)
   138  	if err != nil {
   139  		return 0, fmt.Errorf("import failed: %v", err)
   140  	}
   141  	gopathInstallDir = depP.PkgTargetRoot
   142  	return m.Run(), nil
   143  }
   144  
   145  func TestMain(m *testing.M) {
   146  	// Some of the tests install binaries into a custom GOPATH.
   147  	// That won't work if GOBIN is set.
   148  	os.Unsetenv("GOBIN")
   149  
   150  	flag.Parse()
   151  	exitCode, err := testMain(m)
   152  	if err != nil {
   153  		log.Fatal(err)
   154  	}
   155  	os.Exit(exitCode)
   156  }
   157  
   158  // The shared library was built at the expected location.
   159  func TestSOBuilt(t *testing.T) {
   160  	_, err := os.Stat(filepath.Join(gorootInstallDir, soname))
   161  	if err != nil {
   162  		t.Error(err)
   163  	}
   164  }
   165  
   166  // The install command should have created a "shlibname" file for the
   167  // listed packages (and runtime/cgo) indicating the name of the shared
   168  // library containing it.
   169  func TestShlibnameFiles(t *testing.T) {
   170  	pkgs := append([]string{}, minpkgs...)
   171  	pkgs = append(pkgs, "runtime/cgo")
   172  	for _, pkg := range pkgs {
   173  		shlibnamefile := filepath.Join(gorootInstallDir, pkg+".shlibname")
   174  		contentsb, err := ioutil.ReadFile(shlibnamefile)
   175  		if err != nil {
   176  			t.Errorf("error reading shlibnamefile for %s: %v", pkg, err)
   177  			continue
   178  		}
   179  		contents := strings.TrimSpace(string(contentsb))
   180  		if contents != soname {
   181  			t.Errorf("shlibnamefile for %s has wrong contents: %q", pkg, contents)
   182  		}
   183  	}
   184  }
   185  
   186  // Is a given offset into the file contained in a loaded segment?
   187  func isOffsetLoaded(f *elf.File, offset uint64) bool {
   188  	for _, prog := range f.Progs {
   189  		if prog.Type == elf.PT_LOAD {
   190  			if prog.Off <= offset && offset < prog.Off+prog.Filesz {
   191  				return true
   192  			}
   193  		}
   194  	}
   195  	return false
   196  }
   197  
   198  func rnd(v int32, r int32) int32 {
   199  	if r <= 0 {
   200  		return v
   201  	}
   202  	v += r - 1
   203  	c := v % r
   204  	if c < 0 {
   205  		c += r
   206  	}
   207  	v -= c
   208  	return v
   209  }
   210  
   211  func readwithpad(r io.Reader, sz int32) ([]byte, error) {
   212  	data := make([]byte, rnd(sz, 4))
   213  	_, err := io.ReadFull(r, data)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	data = data[:sz]
   218  	return data, nil
   219  }
   220  
   221  type note struct {
   222  	name    string
   223  	tag     int32
   224  	desc    string
   225  	section *elf.Section
   226  }
   227  
   228  // Read all notes from f. As ELF section names are not supposed to be special, one
   229  // looks for a particular note by scanning all SHT_NOTE sections looking for a note
   230  // with a particular "name" and "tag".
   231  func readNotes(f *elf.File) ([]*note, error) {
   232  	var notes []*note
   233  	for _, sect := range f.Sections {
   234  		if sect.Type != elf.SHT_NOTE {
   235  			continue
   236  		}
   237  		r := sect.Open()
   238  		for {
   239  			var namesize, descsize, tag int32
   240  			err := binary.Read(r, f.ByteOrder, &namesize)
   241  			if err != nil {
   242  				if err == io.EOF {
   243  					break
   244  				}
   245  				return nil, fmt.Errorf("read namesize failed:", err)
   246  			}
   247  			err = binary.Read(r, f.ByteOrder, &descsize)
   248  			if err != nil {
   249  				return nil, fmt.Errorf("read descsize failed:", err)
   250  			}
   251  			err = binary.Read(r, f.ByteOrder, &tag)
   252  			if err != nil {
   253  				return nil, fmt.Errorf("read type failed:", err)
   254  			}
   255  			name, err := readwithpad(r, namesize)
   256  			if err != nil {
   257  				return nil, fmt.Errorf("read name failed:", err)
   258  			}
   259  			desc, err := readwithpad(r, descsize)
   260  			if err != nil {
   261  				return nil, fmt.Errorf("read desc failed:", err)
   262  			}
   263  			notes = append(notes, &note{name: string(name), tag: tag, desc: string(desc), section: sect})
   264  		}
   265  	}
   266  	return notes, nil
   267  }
   268  
   269  func dynStrings(path string, flag elf.DynTag) []string {
   270  	f, err := elf.Open(path)
   271  	defer f.Close()
   272  	if err != nil {
   273  		log.Fatal("elf.Open failed: ", err)
   274  	}
   275  	dynstrings, err := f.DynString(flag)
   276  	if err != nil {
   277  		log.Fatal("dynstring failed: ", err)
   278  	}
   279  	return dynstrings
   280  }
   281  
   282  func AssertIsLinkedToRegexp(t *testing.T, path string, re *regexp.Regexp) {
   283  	for _, dynstring := range dynStrings(path, elf.DT_NEEDED) {
   284  		if re.MatchString(dynstring) {
   285  			return
   286  		}
   287  	}
   288  	t.Errorf("%s is not linked to anything matching %v", path, re)
   289  }
   290  
   291  func AssertIsLinkedTo(t *testing.T, path, lib string) {
   292  	AssertIsLinkedToRegexp(t, path, regexp.MustCompile(regexp.QuoteMeta(lib)))
   293  }
   294  
   295  func AssertHasRPath(t *testing.T, path, dir string) {
   296  	for _, tag := range []elf.DynTag{elf.DT_RPATH, elf.DT_RUNPATH} {
   297  		for _, dynstring := range dynStrings(path, tag) {
   298  			for _, rpath := range strings.Split(dynstring, ":") {
   299  				if filepath.Clean(rpath) == filepath.Clean(dir) {
   300  					return
   301  				}
   302  			}
   303  		}
   304  	}
   305  	t.Errorf("%s does not have rpath %s", path, dir)
   306  }
   307  
   308  // Build a trivial program that links against the shared runtime and check it runs.
   309  func TestTrivialExecutable(t *testing.T) {
   310  	goCmd(t, "install", "-linkshared", "trivial")
   311  	run(t, "trivial executable", "./bin/trivial")
   312  	AssertIsLinkedTo(t, "./bin/trivial", soname)
   313  	AssertHasRPath(t, "./bin/trivial", gorootInstallDir)
   314  }
   315  
   316  // Build an executable that uses cgo linked against the shared runtime and check it
   317  // runs.
   318  func TestCgoExecutable(t *testing.T) {
   319  	goCmd(t, "install", "-linkshared", "execgo")
   320  	run(t, "cgo executable", "./bin/execgo")
   321  }
   322  
   323  // Build a GOPATH package into a shared library that links against the goroot runtime
   324  // and an executable that links against both.
   325  func TestGopathShlib(t *testing.T) {
   326  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   327  	AssertIsLinkedTo(t, filepath.Join(gopathInstallDir, "libdep.so"), soname)
   328  	goCmd(t, "install", "-linkshared", "exe")
   329  	AssertIsLinkedTo(t, "./bin/exe", soname)
   330  	AssertIsLinkedTo(t, "./bin/exe", "libdep.so")
   331  	AssertHasRPath(t, "./bin/exe", gorootInstallDir)
   332  	AssertHasRPath(t, "./bin/exe", gopathInstallDir)
   333  	// And check it runs.
   334  	run(t, "executable linked to GOPATH library", "./bin/exe")
   335  }
   336  
   337  // The shared library contains a note listing the packages it contains in a section
   338  // that is not mapped into memory.
   339  func testPkgListNote(t *testing.T, f *elf.File, note *note) {
   340  	if note.section.Flags != 0 {
   341  		t.Errorf("package list section has flags %v", note.section.Flags)
   342  	}
   343  	if isOffsetLoaded(f, note.section.Offset) {
   344  		t.Errorf("package list section contained in PT_LOAD segment")
   345  	}
   346  	if note.desc != "dep\n" {
   347  		t.Errorf("incorrect package list %q", note.desc)
   348  	}
   349  }
   350  
   351  // The shared library contains a note containing the ABI hash that is mapped into
   352  // memory and there is a local symbol called go.link.abihashbytes that points 16
   353  // bytes into it.
   354  func testABIHashNote(t *testing.T, f *elf.File, note *note) {
   355  	if note.section.Flags != elf.SHF_ALLOC {
   356  		t.Errorf("abi hash section has flags %v", note.section.Flags)
   357  	}
   358  	if !isOffsetLoaded(f, note.section.Offset) {
   359  		t.Errorf("abihash section not contained in PT_LOAD segment")
   360  	}
   361  	var hashbytes elf.Symbol
   362  	symbols, err := f.Symbols()
   363  	if err != nil {
   364  		t.Errorf("error reading symbols %v", err)
   365  		return
   366  	}
   367  	for _, sym := range symbols {
   368  		if sym.Name == "go.link.abihashbytes" {
   369  			hashbytes = sym
   370  		}
   371  	}
   372  	if hashbytes.Name == "" {
   373  		t.Errorf("no symbol called go.link.abihashbytes")
   374  		return
   375  	}
   376  	if elf.ST_BIND(hashbytes.Info) != elf.STB_LOCAL {
   377  		t.Errorf("%s has incorrect binding %v", hashbytes.Name, elf.ST_BIND(hashbytes.Info))
   378  	}
   379  	if f.Sections[hashbytes.Section] != note.section {
   380  		t.Errorf("%s has incorrect section %v", hashbytes.Name, f.Sections[hashbytes.Section].Name)
   381  	}
   382  	if hashbytes.Value-note.section.Addr != 16 {
   383  		t.Errorf("%s has incorrect offset into section %d", hashbytes.Name, hashbytes.Value-note.section.Addr)
   384  	}
   385  }
   386  
   387  // A Go shared library contains a note indicating which other Go shared libraries it
   388  // was linked against in an unmapped section.
   389  func testDepsNote(t *testing.T, f *elf.File, note *note) {
   390  	if note.section.Flags != 0 {
   391  		t.Errorf("package list section has flags %v", note.section.Flags)
   392  	}
   393  	if isOffsetLoaded(f, note.section.Offset) {
   394  		t.Errorf("package list section contained in PT_LOAD segment")
   395  	}
   396  	// libdep.so just links against the lib containing the runtime.
   397  	if note.desc != soname {
   398  		t.Errorf("incorrect dependency list %q", note.desc)
   399  	}
   400  }
   401  
   402  // The shared library contains notes with defined contents; see above.
   403  func TestNotes(t *testing.T) {
   404  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   405  	f, err := elf.Open(filepath.Join(gopathInstallDir, "libdep.so"))
   406  	if err != nil {
   407  		t.Fatal(err)
   408  	}
   409  	defer f.Close()
   410  	notes, err := readNotes(f)
   411  	if err != nil {
   412  		t.Fatal(err)
   413  	}
   414  	pkgListNoteFound := false
   415  	abiHashNoteFound := false
   416  	depsNoteFound := false
   417  	for _, note := range notes {
   418  		if note.name != "Go\x00\x00" {
   419  			continue
   420  		}
   421  		switch note.tag {
   422  		case 1: // ELF_NOTE_GOPKGLIST_TAG
   423  			if pkgListNoteFound {
   424  				t.Error("multiple package list notes")
   425  			}
   426  			testPkgListNote(t, f, note)
   427  			pkgListNoteFound = true
   428  		case 2: // ELF_NOTE_GOABIHASH_TAG
   429  			if abiHashNoteFound {
   430  				t.Error("multiple abi hash notes")
   431  			}
   432  			testABIHashNote(t, f, note)
   433  			abiHashNoteFound = true
   434  		case 3: // ELF_NOTE_GODEPS_TAG
   435  			if depsNoteFound {
   436  				t.Error("multiple abi hash notes")
   437  			}
   438  			testDepsNote(t, f, note)
   439  			depsNoteFound = true
   440  		}
   441  	}
   442  	if !pkgListNoteFound {
   443  		t.Error("package list note not found")
   444  	}
   445  	if !abiHashNoteFound {
   446  		t.Error("abi hash note not found")
   447  	}
   448  	if !depsNoteFound {
   449  		t.Error("deps note not found")
   450  	}
   451  }
   452  
   453  // Build a GOPATH package (dep) into a shared library that links against the goroot
   454  // runtime, another package (dep2) that links against the first, and and an
   455  // executable that links against dep2.
   456  func TestTwoGopathShlibs(t *testing.T) {
   457  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   458  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep2")
   459  	goCmd(t, "install", "-linkshared", "exe2")
   460  	run(t, "executable linked to GOPATH library", "./bin/exe2")
   461  }
   462  
   463  // Build a GOPATH package into a shared library with gccgo and an executable that
   464  // links against it.
   465  func TestGoPathShlibGccgo(t *testing.T) {
   466  	gccgoName := os.Getenv("GCCGO")
   467  	if gccgoName == "" {
   468  		gccgoName = "gccgo"
   469  	}
   470  	_, err := exec.LookPath(gccgoName)
   471  	if err != nil {
   472  		t.Skip("gccgo not found")
   473  	}
   474  
   475  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   476  
   477  	gccgoContext := build.Default
   478  	gccgoContext.InstallSuffix = suffix + "_fPIC"
   479  	gccgoContext.Compiler = "gccgo"
   480  	gccgoContext.GOPATH = os.Getenv("GOPATH")
   481  	depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
   482  	if err != nil {
   483  		t.Fatalf("import failed: %v", err)
   484  	}
   485  	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
   486  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
   487  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
   488  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe")
   489  	AssertIsLinkedToRegexp(t, "./bin/exe", libgoRE)
   490  	AssertIsLinkedTo(t, "./bin/exe", "libdep.so")
   491  	AssertHasRPath(t, "./bin/exe", gccgoInstallDir)
   492  	// And check it runs.
   493  	run(t, "gccgo-built", "./bin/exe")
   494  }
   495  
   496  // The gccgo version of TestTwoGopathShlibs: build a GOPATH package into a shared
   497  // library with gccgo, another GOPATH package that depends on the first and an
   498  // executable that links the second library.
   499  func TestTwoGopathShlibsGccgo(t *testing.T) {
   500  	gccgoName := os.Getenv("GCCGO")
   501  	if gccgoName == "" {
   502  		gccgoName = "gccgo"
   503  	}
   504  	_, err := exec.LookPath(gccgoName)
   505  	if err != nil {
   506  		t.Skip("gccgo not found")
   507  	}
   508  
   509  	libgoRE := regexp.MustCompile("libgo.so.[0-9]+")
   510  
   511  	gccgoContext := build.Default
   512  	gccgoContext.InstallSuffix = suffix + "_fPIC"
   513  	gccgoContext.Compiler = "gccgo"
   514  	gccgoContext.GOPATH = os.Getenv("GOPATH")
   515  	depP, err := gccgoContext.Import("dep", ".", build.ImportComment)
   516  	if err != nil {
   517  		t.Fatalf("import failed: %v", err)
   518  	}
   519  	gccgoInstallDir := filepath.Join(depP.PkgTargetRoot, "shlibs")
   520  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep")
   521  	goCmd(t, "install", "-compiler=gccgo", "-buildmode=shared", "-linkshared", "dep2")
   522  	goCmd(t, "install", "-compiler=gccgo", "-linkshared", "exe2")
   523  
   524  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep.so"), libgoRE)
   525  	AssertIsLinkedToRegexp(t, filepath.Join(gccgoInstallDir, "libdep2.so"), libgoRE)
   526  	AssertIsLinkedTo(t, filepath.Join(gccgoInstallDir, "libdep2.so"), "libdep.so")
   527  	AssertIsLinkedToRegexp(t, "./bin/exe2", libgoRE)
   528  	AssertIsLinkedTo(t, "./bin/exe2", "libdep2")
   529  	AssertIsLinkedTo(t, "./bin/exe2", "libdep.so")
   530  
   531  	// And check it runs.
   532  	run(t, "gccgo-built", "./bin/exe2")
   533  }
   534  
   535  // Testing rebuilding of shared libraries when they are stale is a bit more
   536  // complicated that it seems like it should be. First, we make everything "old": but
   537  // only a few seconds old, or it might be older than gc (or the runtime source) and
   538  // everything will get rebuilt. Then define a timestamp slightly newer than this
   539  // time, which is what we set the mtime to of a file to cause it to be seen as new,
   540  // and finally another slightly even newer one that we can compare files against to
   541  // see if they have been rebuilt.
   542  var oldTime = time.Now().Add(-9 * time.Second)
   543  var nearlyNew = time.Now().Add(-6 * time.Second)
   544  var stampTime = time.Now().Add(-3 * time.Second)
   545  
   546  // resetFileStamps makes "everything" (bin, src, pkg from GOPATH and the
   547  // test-specific parts of GOROOT) appear old.
   548  func resetFileStamps() {
   549  	chtime := func(path string, info os.FileInfo, err error) error {
   550  		return os.Chtimes(path, oldTime, oldTime)
   551  	}
   552  	reset := func(path string) {
   553  		if err := filepath.Walk(path, chtime); err != nil {
   554  			log.Fatalf("resetFileStamps failed: %v", err)
   555  		}
   556  
   557  	}
   558  	reset("bin")
   559  	reset("pkg")
   560  	reset("src")
   561  	reset(gorootInstallDir)
   562  }
   563  
   564  // touch makes path newer than the "old" time stamp used by resetFileStamps.
   565  func touch(path string) {
   566  	if err := os.Chtimes(path, nearlyNew, nearlyNew); err != nil {
   567  		log.Fatalf("os.Chtimes failed: %v", err)
   568  	}
   569  }
   570  
   571  // isNew returns if the path is newer than the time stamp used by touch.
   572  func isNew(path string) bool {
   573  	fi, err := os.Stat(path)
   574  	if err != nil {
   575  		log.Fatalf("os.Stat failed: %v", err)
   576  	}
   577  	return fi.ModTime().After(stampTime)
   578  }
   579  
   580  // Fail unless path has been rebuilt (i.e. is newer than the time stamp used by
   581  // isNew)
   582  func AssertRebuilt(t *testing.T, msg, path string) {
   583  	if !isNew(path) {
   584  		t.Errorf("%s was not rebuilt (%s)", msg, path)
   585  	}
   586  }
   587  
   588  // Fail if path has been rebuilt (i.e. is newer than the time stamp used by isNew)
   589  func AssertNotRebuilt(t *testing.T, msg, path string) {
   590  	if isNew(path) {
   591  		t.Errorf("%s was rebuilt (%s)", msg, path)
   592  	}
   593  }
   594  
   595  func TestRebuilding(t *testing.T) {
   596  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   597  	goCmd(t, "install", "-linkshared", "exe")
   598  
   599  	// If the source is newer than both the .a file and the .so, both are rebuilt.
   600  	resetFileStamps()
   601  	touch("src/dep/dep.go")
   602  	goCmd(t, "install", "-linkshared", "exe")
   603  	AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "dep.a"))
   604  	AssertRebuilt(t, "new source", filepath.Join(gopathInstallDir, "libdep.so"))
   605  
   606  	// If the .a file is newer than the .so, the .so is rebuilt (but not the .a)
   607  	resetFileStamps()
   608  	touch(filepath.Join(gopathInstallDir, "dep.a"))
   609  	goCmd(t, "install", "-linkshared", "exe")
   610  	AssertNotRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "dep.a"))
   611  	AssertRebuilt(t, "new .a file", filepath.Join(gopathInstallDir, "libdep.so"))
   612  }
   613  
   614  func appendFile(path, content string) {
   615  	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND, 0660)
   616  	if err != nil {
   617  		log.Fatalf("os.OpenFile failed: %v", err)
   618  	}
   619  	defer func() {
   620  		err := f.Close()
   621  		if err != nil {
   622  			log.Fatalf("f.Close failed: %v", err)
   623  		}
   624  	}()
   625  	_, err = f.WriteString(content)
   626  	if err != nil {
   627  		log.Fatalf("f.WriteString failed: %v", err)
   628  	}
   629  }
   630  
   631  func TestABIChecking(t *testing.T) {
   632  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   633  	goCmd(t, "install", "-linkshared", "exe")
   634  
   635  	// If we make an ABI-breaking change to dep and rebuild libp.so but not exe,
   636  	// exe will abort with a complaint on startup.
   637  	// This assumes adding an exported function breaks ABI, which is not true in
   638  	// some senses but suffices for the narrow definition of ABI compatiblity the
   639  	// toolchain uses today.
   640  	resetFileStamps()
   641  	appendFile("src/dep/dep.go", "func ABIBreak() {}\n")
   642  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   643  	c := exec.Command("./bin/exe")
   644  	output, err := c.CombinedOutput()
   645  	if err == nil {
   646  		t.Fatal("executing exe did not fail after ABI break")
   647  	}
   648  	scanner := bufio.NewScanner(bytes.NewReader(output))
   649  	foundMsg := false
   650  	const wantLine = "abi mismatch detected between the executable and libdep.so"
   651  	for scanner.Scan() {
   652  		if scanner.Text() == wantLine {
   653  			foundMsg = true
   654  			break
   655  		}
   656  	}
   657  	if err = scanner.Err(); err != nil {
   658  		t.Errorf("scanner encountered error: %v", err)
   659  	}
   660  	if !foundMsg {
   661  		t.Fatalf("exe failed, but without line %q; got output:\n%s", wantLine, output)
   662  	}
   663  
   664  	// Rebuilding exe makes it work again.
   665  	goCmd(t, "install", "-linkshared", "exe")
   666  	run(t, "rebuilt exe", "./bin/exe")
   667  
   668  	// If we make a change which does not break ABI (such as adding an unexported
   669  	// function) and rebuild libdep.so, exe still works.
   670  	resetFileStamps()
   671  	appendFile("src/dep/dep.go", "func noABIBreak() {}\n")
   672  	goCmd(t, "install", "-buildmode=shared", "-linkshared", "dep")
   673  	run(t, "after non-ABI breaking change", "./bin/exe")
   674  }