github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/misc/cgo/testcarchive/carchive_test.go (about) 1 // Copyright 2016 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 carchive_test 6 7 import ( 8 "bufio" 9 "debug/elf" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 "syscall" 17 "testing" 18 "time" 19 "unicode" 20 ) 21 22 // Program to run. 23 var bin []string 24 25 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). 26 var cc []string 27 28 // An environment with GOPATH=$(pwd). 29 var gopathEnv []string 30 31 // ".exe" on Windows. 32 var exeSuffix string 33 34 var GOOS, GOARCH string 35 var libgodir string 36 37 func init() { 38 GOOS = goEnv("GOOS") 39 GOARCH = goEnv("GOARCH") 40 bin = cmdToRun("./testp") 41 42 ccOut := goEnv("CC") 43 cc = []string{string(ccOut)} 44 45 out := goEnv("GOGCCFLAGS") 46 quote := '\000' 47 start := 0 48 lastSpace := true 49 backslash := false 50 s := string(out) 51 for i, c := range s { 52 if quote == '\000' && unicode.IsSpace(c) { 53 if !lastSpace { 54 cc = append(cc, s[start:i]) 55 lastSpace = true 56 } 57 } else { 58 if lastSpace { 59 start = i 60 lastSpace = false 61 } 62 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 63 quote = c 64 backslash = false 65 } else if !backslash && quote == c { 66 quote = '\000' 67 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 68 backslash = true 69 } else { 70 backslash = false 71 } 72 } 73 } 74 if !lastSpace { 75 cc = append(cc, s[start:]) 76 } 77 78 if GOOS == "darwin" { 79 // For Darwin/ARM. 80 // TODO(crawshaw): can we do better? 81 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) 82 } 83 libgodir = GOOS + "_" + GOARCH 84 switch GOOS { 85 case "darwin": 86 if GOARCH == "arm" || GOARCH == "arm64" { 87 libgodir += "_shared" 88 } 89 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 90 libgodir += "_shared" 91 } 92 cc = append(cc, "-I", filepath.Join("pkg", libgodir)) 93 94 // Build an environment with GOPATH=$(pwd) 95 env := os.Environ() 96 var n []string 97 for _, e := range env { 98 if !strings.HasPrefix(e, "GOPATH=") { 99 n = append(n, e) 100 } 101 } 102 dir, err := os.Getwd() 103 if err != nil { 104 fmt.Fprintln(os.Stderr, err) 105 os.Exit(2) 106 } 107 n = append(n, "GOPATH="+dir) 108 gopathEnv = n 109 110 if GOOS == "windows" { 111 exeSuffix = ".exe" 112 } 113 } 114 115 func goEnv(key string) string { 116 out, err := exec.Command("go", "env", key).Output() 117 if err != nil { 118 fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err) 119 fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr) 120 os.Exit(2) 121 } 122 return strings.TrimSpace(string(out)) 123 } 124 125 func cmdToRun(name string) []string { 126 execScript := "go_" + goEnv("GOOS") + "_" + goEnv("GOARCH") + "_exec" 127 executor, err := exec.LookPath(execScript) 128 if err != nil { 129 return []string{name} 130 } 131 return []string{executor, name} 132 } 133 134 func testInstall(t *testing.T, exe, libgoa, libgoh string, buildcmd ...string) { 135 cmd := exec.Command(buildcmd[0], buildcmd[1:]...) 136 cmd.Env = gopathEnv 137 if out, err := cmd.CombinedOutput(); err != nil { 138 t.Logf("%s", out) 139 t.Fatal(err) 140 } 141 defer func() { 142 os.Remove(libgoa) 143 os.Remove(libgoh) 144 }() 145 146 ccArgs := append(cc, "-o", exe, "main.c") 147 if GOOS == "windows" { 148 ccArgs = append(ccArgs, "main_windows.c", libgoa, "-lntdll", "-lws2_32", "-lwinmm") 149 } else { 150 ccArgs = append(ccArgs, "main_unix.c", libgoa) 151 } 152 t.Log(ccArgs) 153 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 154 t.Logf("%s", out) 155 t.Fatal(err) 156 } 157 defer os.Remove(exe) 158 159 binArgs := append(cmdToRun(exe), "arg1", "arg2") 160 if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil { 161 t.Logf("%s", out) 162 t.Fatal(err) 163 } 164 } 165 166 func TestInstall(t *testing.T) { 167 defer os.RemoveAll("pkg") 168 169 testInstall(t, "./testp1"+exeSuffix, 170 filepath.Join("pkg", libgodir, "libgo.a"), 171 filepath.Join("pkg", libgodir, "libgo.h"), 172 "go", "install", "-buildmode=c-archive", "libgo") 173 174 // Test building libgo other than installing it. 175 // Header files are now present. 176 testInstall(t, "./testp2"+exeSuffix, "libgo.a", "libgo.h", 177 "go", "build", "-buildmode=c-archive", filepath.Join("src", "libgo", "libgo.go")) 178 179 testInstall(t, "./testp3"+exeSuffix, "libgo.a", "libgo.h", 180 "go", "build", "-buildmode=c-archive", "-o", "libgo.a", "libgo") 181 } 182 183 func TestEarlySignalHandler(t *testing.T) { 184 switch GOOS { 185 case "darwin": 186 switch GOARCH { 187 case "arm", "arm64": 188 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 189 } 190 case "windows": 191 t.Skip("skipping signal test on Windows") 192 } 193 194 defer func() { 195 os.Remove("libgo2.a") 196 os.Remove("libgo2.h") 197 os.Remove("testp") 198 os.RemoveAll("pkg") 199 }() 200 201 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 202 cmd.Env = gopathEnv 203 if out, err := cmd.CombinedOutput(); err != nil { 204 t.Logf("%s", out) 205 t.Fatal(err) 206 } 207 208 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main2.c", "libgo2.a") 209 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 210 t.Logf("%s", out) 211 t.Fatal(err) 212 } 213 214 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 215 t.Logf("%s", out) 216 t.Fatal(err) 217 } 218 } 219 220 func TestSignalForwarding(t *testing.T) { 221 switch GOOS { 222 case "darwin": 223 switch GOARCH { 224 case "arm", "arm64": 225 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 226 } 227 case "windows": 228 t.Skip("skipping signal test on Windows") 229 } 230 231 defer func() { 232 os.Remove("libgo2.a") 233 os.Remove("libgo2.h") 234 os.Remove("testp") 235 os.RemoveAll("pkg") 236 }() 237 238 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 239 cmd.Env = gopathEnv 240 if out, err := cmd.CombinedOutput(); err != nil { 241 t.Logf("%s", out) 242 t.Fatal(err) 243 } 244 245 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") 246 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 247 t.Logf("%s", out) 248 t.Fatal(err) 249 } 250 251 cmd = exec.Command(bin[0], append(bin[1:], "1")...) 252 253 out, err := cmd.CombinedOutput() 254 255 if err == nil { 256 t.Logf("%s", out) 257 t.Error("test program succeeded unexpectedly") 258 } else if ee, ok := err.(*exec.ExitError); !ok { 259 t.Logf("%s", out) 260 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 261 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 262 t.Logf("%s", out) 263 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 264 } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV { 265 t.Logf("%s", out) 266 t.Errorf("got %v; expected SIGSEGV", ee) 267 } 268 269 // Test SIGPIPE forwarding 270 cmd = exec.Command(bin[0], append(bin[1:], "3")...) 271 272 out, err = cmd.CombinedOutput() 273 274 if err == nil { 275 t.Logf("%s", out) 276 t.Error("test program succeeded unexpectedly") 277 } else if ee, ok := err.(*exec.ExitError); !ok { 278 t.Logf("%s", out) 279 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 280 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 281 t.Logf("%s", out) 282 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 283 } else if !ws.Signaled() || ws.Signal() != syscall.SIGPIPE { 284 t.Logf("%s", out) 285 t.Errorf("got %v; expected SIGPIPE", ee) 286 } 287 } 288 289 func TestSignalForwardingExternal(t *testing.T) { 290 switch GOOS { 291 case "darwin": 292 switch GOARCH { 293 case "arm", "arm64": 294 t.Skipf("skipping on %s/%s; see https://golang.org/issue/13701", GOOS, GOARCH) 295 } 296 case "windows": 297 t.Skip("skipping signal test on Windows") 298 } 299 300 defer func() { 301 os.Remove("libgo2.a") 302 os.Remove("libgo2.h") 303 os.Remove("testp") 304 os.RemoveAll("pkg") 305 }() 306 307 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo2.a", "libgo2") 308 cmd.Env = gopathEnv 309 if out, err := cmd.CombinedOutput(); err != nil { 310 t.Logf("%s", out) 311 t.Fatal(err) 312 } 313 314 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main5.c", "libgo2.a") 315 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 316 t.Logf("%s", out) 317 t.Fatal(err) 318 } 319 320 // We want to send the process a signal and see if it dies. 321 // Normally the signal goes to the C thread, the Go signal 322 // handler picks it up, sees that it is running in a C thread, 323 // and the program dies. Unfortunately, occasionally the 324 // signal is delivered to a Go thread, which winds up 325 // discarding it because it was sent by another program and 326 // there is no Go handler for it. To avoid this, run the 327 // program several times in the hopes that it will eventually 328 // fail. 329 const tries = 20 330 for i := 0; i < tries; i++ { 331 cmd = exec.Command(bin[0], append(bin[1:], "2")...) 332 333 stderr, err := cmd.StderrPipe() 334 if err != nil { 335 t.Fatal(err) 336 } 337 defer stderr.Close() 338 339 r := bufio.NewReader(stderr) 340 341 err = cmd.Start() 342 343 if err != nil { 344 t.Fatal(err) 345 } 346 347 // Wait for trigger to ensure that the process is started. 348 ok, err := r.ReadString('\n') 349 350 // Verify trigger. 351 if err != nil || ok != "OK\n" { 352 t.Fatalf("Did not receive OK signal") 353 } 354 355 // Give the program a chance to enter the sleep function. 356 time.Sleep(time.Millisecond) 357 358 cmd.Process.Signal(syscall.SIGSEGV) 359 360 err = cmd.Wait() 361 362 if err == nil { 363 continue 364 } 365 366 if ee, ok := err.(*exec.ExitError); !ok { 367 t.Errorf("error (%v) has type %T; expected exec.ExitError", err, err) 368 } else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok { 369 t.Errorf("error.Sys (%v) has type %T; expected syscall.WaitStatus", ee.Sys(), ee.Sys()) 370 } else if !ws.Signaled() || ws.Signal() != syscall.SIGSEGV { 371 t.Errorf("got %v; expected SIGSEGV", ee) 372 } else { 373 // We got the error we expected. 374 return 375 } 376 } 377 378 t.Errorf("program succeeded unexpectedly %d times", tries) 379 } 380 381 func TestOsSignal(t *testing.T) { 382 switch GOOS { 383 case "windows": 384 t.Skip("skipping signal test on Windows") 385 } 386 387 defer func() { 388 os.Remove("libgo3.a") 389 os.Remove("libgo3.h") 390 os.Remove("testp") 391 os.RemoveAll("pkg") 392 }() 393 394 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo3.a", "libgo3") 395 cmd.Env = gopathEnv 396 if out, err := cmd.CombinedOutput(); err != nil { 397 t.Logf("%s", out) 398 t.Fatal(err) 399 } 400 401 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main3.c", "libgo3.a") 402 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 403 t.Logf("%s", out) 404 t.Fatal(err) 405 } 406 407 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 408 t.Logf("%s", out) 409 t.Fatal(err) 410 } 411 } 412 413 func TestSigaltstack(t *testing.T) { 414 switch GOOS { 415 case "windows": 416 t.Skip("skipping signal test on Windows") 417 } 418 419 defer func() { 420 os.Remove("libgo4.a") 421 os.Remove("libgo4.h") 422 os.Remove("testp") 423 os.RemoveAll("pkg") 424 }() 425 426 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo4.a", "libgo4") 427 cmd.Env = gopathEnv 428 if out, err := cmd.CombinedOutput(); err != nil { 429 t.Logf("%s", out) 430 t.Fatal(err) 431 } 432 433 ccArgs := append(cc, "-o", "testp"+exeSuffix, "main4.c", "libgo4.a") 434 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 435 t.Logf("%s", out) 436 t.Fatal(err) 437 } 438 439 if out, err := exec.Command(bin[0], bin[1:]...).CombinedOutput(); err != nil { 440 t.Logf("%s", out) 441 t.Fatal(err) 442 } 443 } 444 445 const testar = `#!/usr/bin/env bash 446 while expr $1 : '[-]' >/dev/null; do 447 shift 448 done 449 echo "testar" > $1 450 echo "testar" > PWD/testar.ran 451 ` 452 453 func TestExtar(t *testing.T) { 454 switch GOOS { 455 case "windows": 456 t.Skip("skipping signal test on Windows") 457 } 458 459 defer func() { 460 os.Remove("libgo4.a") 461 os.Remove("libgo4.h") 462 os.Remove("testar") 463 os.Remove("testar.ran") 464 os.RemoveAll("pkg") 465 }() 466 467 os.Remove("testar") 468 dir, err := os.Getwd() 469 if err != nil { 470 t.Fatal(err) 471 } 472 s := strings.Replace(testar, "PWD", dir, 1) 473 if err := ioutil.WriteFile("testar", []byte(s), 0777); err != nil { 474 t.Fatal(err) 475 } 476 477 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-ldflags=-extar="+filepath.Join(dir, "testar"), "-o", "libgo4.a", "libgo4") 478 cmd.Env = gopathEnv 479 if out, err := cmd.CombinedOutput(); err != nil { 480 t.Logf("%s", out) 481 t.Fatal(err) 482 } 483 484 if _, err := os.Stat("testar.ran"); err != nil { 485 if os.IsNotExist(err) { 486 t.Error("testar does not exist after go build") 487 } else { 488 t.Errorf("error checking testar: %v", err) 489 } 490 } 491 } 492 493 func TestPIE(t *testing.T) { 494 switch GOOS { 495 case "windows", "darwin", "plan9": 496 t.Skipf("skipping PIE test on %s", GOOS) 497 } 498 499 defer func() { 500 os.Remove("testp" + exeSuffix) 501 os.RemoveAll("pkg") 502 }() 503 504 cmd := exec.Command("go", "install", "-buildmode=c-archive", "libgo") 505 cmd.Env = gopathEnv 506 if out, err := cmd.CombinedOutput(); err != nil { 507 t.Logf("%s", out) 508 t.Fatal(err) 509 } 510 511 ccArgs := append(cc, "-fPIE", "-pie", "-o", "testp"+exeSuffix, "main.c", "main_unix.c", filepath.Join("pkg", libgodir, "libgo.a")) 512 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 513 t.Logf("%s", out) 514 t.Fatal(err) 515 } 516 517 binArgs := append(bin, "arg1", "arg2") 518 if out, err := exec.Command(binArgs[0], binArgs[1:]...).CombinedOutput(); err != nil { 519 t.Logf("%s", out) 520 t.Fatal(err) 521 } 522 523 f, err := elf.Open("testp" + exeSuffix) 524 if err != nil { 525 t.Fatal("elf.Open failed: ", err) 526 } 527 defer f.Close() 528 if hasDynTag(t, f, elf.DT_TEXTREL) { 529 t.Errorf("%s has DT_TEXTREL flag", "testp"+exeSuffix) 530 } 531 } 532 533 func hasDynTag(t *testing.T, f *elf.File, tag elf.DynTag) bool { 534 ds := f.SectionByType(elf.SHT_DYNAMIC) 535 if ds == nil { 536 t.Error("no SHT_DYNAMIC section") 537 return false 538 } 539 d, err := ds.Data() 540 if err != nil { 541 t.Errorf("can't read SHT_DYNAMIC contents: %v", err) 542 return false 543 } 544 for len(d) > 0 { 545 var t elf.DynTag 546 switch f.Class { 547 case elf.ELFCLASS32: 548 t = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 549 d = d[8:] 550 case elf.ELFCLASS64: 551 t = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 552 d = d[16:] 553 } 554 if t == tag { 555 return true 556 } 557 } 558 return false 559 } 560 561 func TestSIGPROF(t *testing.T) { 562 switch GOOS { 563 case "windows", "plan9": 564 t.Skipf("skipping SIGPROF test on %s", GOOS) 565 } 566 567 t.Parallel() 568 569 defer func() { 570 os.Remove("testp6" + exeSuffix) 571 os.Remove("libgo6.a") 572 os.Remove("libgo6.h") 573 }() 574 575 cmd := exec.Command("go", "build", "-buildmode=c-archive", "-o", "libgo6.a", "libgo6") 576 cmd.Env = gopathEnv 577 if out, err := cmd.CombinedOutput(); err != nil { 578 t.Logf("%s", out) 579 t.Fatal(err) 580 } 581 582 ccArgs := append(cc, "-o", "testp6"+exeSuffix, "main6.c", "libgo6.a") 583 if out, err := exec.Command(ccArgs[0], ccArgs[1:]...).CombinedOutput(); err != nil { 584 t.Logf("%s", out) 585 t.Fatal(err) 586 } 587 588 argv := cmdToRun("./testp6") 589 cmd = exec.Command(argv[0], argv[1:]...) 590 if out, err := cmd.CombinedOutput(); err != nil { 591 t.Logf("%s", out) 592 t.Fatal(err) 593 } 594 }