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, ¬e{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 } 675