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