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