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