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