github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/misc/cgo/testcshared/cshared_test.go (about) 1 // Copyright 2017 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 cshared_test 6 7 import ( 8 "debug/elf" 9 "fmt" 10 "io/ioutil" 11 "log" 12 "os" 13 "os/exec" 14 "path" 15 "path/filepath" 16 "strings" 17 "sync" 18 "testing" 19 "unicode" 20 ) 21 22 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). 23 var cc []string 24 25 // An environment with GOPATH=$(pwd). 26 var gopathEnv []string 27 28 // ".exe" on Windows. 29 var exeSuffix string 30 31 var GOOS, GOARCH, GOROOT string 32 var installdir, androiddir string 33 var libSuffix, libgoname string 34 35 func TestMain(m *testing.M) { 36 GOOS = goEnv("GOOS") 37 GOARCH = goEnv("GOARCH") 38 GOROOT = goEnv("GOROOT") 39 40 if _, err := os.Stat(GOROOT); os.IsNotExist(err) { 41 log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT) 42 } 43 44 // Directory where cgo headers and outputs will be installed. 45 // The installation directory format varies depending on the platform. 46 installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared", GOOS, GOARCH)) 47 switch GOOS { 48 case "darwin": 49 libSuffix = "dylib" 50 case "windows": 51 libSuffix = "dll" 52 default: 53 libSuffix = "so" 54 installdir = path.Join("pkg", fmt.Sprintf("%s_%s_testcshared_shared", GOOS, GOARCH)) 55 } 56 57 androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid()) 58 if GOOS == "android" { 59 args := append(adbCmd(), "shell", "mkdir", "-p", androiddir) 60 cmd := exec.Command(args[0], args[1:]...) 61 out, err := cmd.CombinedOutput() 62 if err != nil { 63 log.Fatalf("setupAndroid failed: %v\n%s\n", err, out) 64 } 65 } 66 67 libgoname = "libgo." + libSuffix 68 69 cc = []string{goEnv("CC")} 70 71 out := goEnv("GOGCCFLAGS") 72 quote := '\000' 73 start := 0 74 lastSpace := true 75 backslash := false 76 s := string(out) 77 for i, c := range s { 78 if quote == '\000' && unicode.IsSpace(c) { 79 if !lastSpace { 80 cc = append(cc, s[start:i]) 81 lastSpace = true 82 } 83 } else { 84 if lastSpace { 85 start = i 86 lastSpace = false 87 } 88 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 89 quote = c 90 backslash = false 91 } else if !backslash && quote == c { 92 quote = '\000' 93 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 94 backslash = true 95 } else { 96 backslash = false 97 } 98 } 99 } 100 if !lastSpace { 101 cc = append(cc, s[start:]) 102 } 103 104 switch GOOS { 105 case "darwin": 106 // For Darwin/ARM. 107 // TODO(crawshaw): can we do better? 108 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) 109 case "android": 110 cc = append(cc, "-pie", "-fuse-ld=gold") 111 } 112 libgodir := GOOS + "_" + GOARCH 113 switch GOOS { 114 case "darwin": 115 if GOARCH == "arm" || GOARCH == "arm64" { 116 libgodir += "_shared" 117 } 118 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris": 119 libgodir += "_shared" 120 } 121 cc = append(cc, "-I", filepath.Join("pkg", libgodir)) 122 123 // Build an environment with GOPATH=$(pwd) 124 dir, err := os.Getwd() 125 if err != nil { 126 fmt.Fprintln(os.Stderr, err) 127 os.Exit(2) 128 } 129 gopathEnv = append(os.Environ(), "GOPATH="+dir) 130 131 if GOOS == "windows" { 132 exeSuffix = ".exe" 133 } 134 135 st := m.Run() 136 137 os.Remove(libgoname) 138 os.RemoveAll("pkg") 139 cleanupHeaders() 140 cleanupAndroid() 141 142 os.Exit(st) 143 } 144 145 func goEnv(key string) string { 146 out, err := exec.Command("go", "env", key).Output() 147 if err != nil { 148 fmt.Fprintf(os.Stderr, "go env %s failed:\n%s", key, err) 149 fmt.Fprintf(os.Stderr, "%s", err.(*exec.ExitError).Stderr) 150 os.Exit(2) 151 } 152 return strings.TrimSpace(string(out)) 153 } 154 155 func cmdToRun(name string) string { 156 return "./" + name + exeSuffix 157 } 158 159 func adbCmd() []string { 160 cmd := []string{"adb"} 161 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" { 162 cmd = append(cmd, strings.Split(flags, " ")...) 163 } 164 return cmd 165 } 166 167 func adbPush(t *testing.T, filename string) { 168 if GOOS != "android" { 169 return 170 } 171 args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename)) 172 cmd := exec.Command(args[0], args[1:]...) 173 if out, err := cmd.CombinedOutput(); err != nil { 174 t.Fatalf("adb command failed: %v\n%s\n", err, out) 175 } 176 } 177 178 func adbRun(t *testing.T, env []string, adbargs ...string) string { 179 if GOOS != "android" { 180 t.Fatalf("trying to run adb command when operating system is not android.") 181 } 182 args := append(adbCmd(), "shell") 183 // Propagate LD_LIBRARY_PATH to the adb shell invocation. 184 for _, e := range env { 185 if strings.Index(e, "LD_LIBRARY_PATH=") != -1 { 186 adbargs = append([]string{e}, adbargs...) 187 break 188 } 189 } 190 shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " ")) 191 args = append(args, shellcmd) 192 cmd := exec.Command(args[0], args[1:]...) 193 out, err := cmd.CombinedOutput() 194 if err != nil { 195 t.Fatalf("adb command failed: %v\n%s\n", err, out) 196 } 197 return strings.Replace(string(out), "\r", "", -1) 198 } 199 200 func run(t *testing.T, env []string, args ...string) string { 201 t.Helper() 202 cmd := exec.Command(args[0], args[1:]...) 203 cmd.Env = env 204 205 if GOOS != "windows" { 206 // TestUnexportedSymbols relies on file descriptor 30 207 // being closed when the program starts, so enforce 208 // that in all cases. (The first three descriptors are 209 // stdin/stdout/stderr, so we just need to make sure 210 // that cmd.ExtraFiles[27] exists and is nil.) 211 cmd.ExtraFiles = make([]*os.File, 28) 212 } 213 214 out, err := cmd.CombinedOutput() 215 if err != nil { 216 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out) 217 } else { 218 t.Logf("run: %v", args) 219 } 220 return string(out) 221 } 222 223 func runExe(t *testing.T, env []string, args ...string) string { 224 t.Helper() 225 if GOOS == "android" { 226 return adbRun(t, env, args...) 227 } 228 return run(t, env, args...) 229 } 230 231 func runCC(t *testing.T, args ...string) string { 232 t.Helper() 233 // This function is run in parallel, so append to a copy of cc 234 // rather than cc itself. 235 return run(t, nil, append(append([]string(nil), cc...), args...)...) 236 } 237 238 func createHeaders() error { 239 args := []string{"go", "install", "-i", "-buildmode=c-shared", 240 "-installsuffix", "testcshared", "libgo"} 241 cmd := exec.Command(args[0], args[1:]...) 242 cmd.Env = gopathEnv 243 out, err := cmd.CombinedOutput() 244 if err != nil { 245 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 246 } 247 248 args = []string{"go", "build", "-buildmode=c-shared", 249 "-installsuffix", "testcshared", 250 "-o", libgoname, 251 filepath.Join("src", "libgo", "libgo.go")} 252 cmd = exec.Command(args[0], args[1:]...) 253 cmd.Env = gopathEnv 254 out, err = cmd.CombinedOutput() 255 if err != nil { 256 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 257 } 258 259 if GOOS == "android" { 260 args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname)) 261 cmd = exec.Command(args[0], args[1:]...) 262 out, err = cmd.CombinedOutput() 263 if err != nil { 264 return fmt.Errorf("adb command failed: %v\n%s\n", err, out) 265 } 266 } 267 268 return nil 269 } 270 271 var ( 272 headersOnce sync.Once 273 headersErr error 274 ) 275 276 func createHeadersOnce(t *testing.T) { 277 headersOnce.Do(func() { 278 headersErr = createHeaders() 279 }) 280 if headersErr != nil { 281 t.Fatal(headersErr) 282 } 283 } 284 285 func cleanupHeaders() { 286 os.Remove("libgo.h") 287 } 288 289 func cleanupAndroid() { 290 if GOOS != "android" { 291 return 292 } 293 args := append(adbCmd(), "shell", "rm", "-rf", androiddir) 294 cmd := exec.Command(args[0], args[1:]...) 295 out, err := cmd.CombinedOutput() 296 if err != nil { 297 log.Fatalf("cleanupAndroid failed: %v\n%s\n", err, out) 298 } 299 } 300 301 // test0: exported symbols in shared lib are accessible. 302 func TestExportedSymbols(t *testing.T) { 303 t.Parallel() 304 305 cmd := "testp0" 306 bin := cmdToRun(cmd) 307 308 createHeadersOnce(t) 309 310 runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname) 311 adbPush(t, cmd) 312 313 defer os.Remove(bin) 314 315 out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin) 316 if strings.TrimSpace(out) != "PASS" { 317 t.Error(out) 318 } 319 } 320 321 // test1: shared library can be dynamically loaded and exported symbols are accessible. 322 func TestExportedSymbolsWithDynamicLoad(t *testing.T) { 323 t.Parallel() 324 325 if GOOS == "windows" { 326 t.Logf("Skipping on %s", GOOS) 327 return 328 } 329 330 cmd := "testp1" 331 bin := cmdToRun(cmd) 332 333 createHeadersOnce(t) 334 335 if GOOS != "freebsd" { 336 runCC(t, "-o", cmd, "main1.c", "-ldl") 337 } else { 338 runCC(t, "-o", cmd, "main1.c") 339 } 340 adbPush(t, cmd) 341 342 defer os.Remove(bin) 343 344 out := runExe(t, nil, bin, "./"+libgoname) 345 if strings.TrimSpace(out) != "PASS" { 346 t.Error(out) 347 } 348 } 349 350 // test2: tests libgo2 which does not export any functions. 351 func TestUnexportedSymbols(t *testing.T) { 352 t.Parallel() 353 354 if GOOS == "windows" { 355 t.Logf("Skipping on %s", GOOS) 356 return 357 } 358 359 cmd := "testp2" 360 bin := cmdToRun(cmd) 361 libname := "libgo2." + libSuffix 362 363 run(t, 364 gopathEnv, 365 "go", "build", 366 "-buildmode=c-shared", 367 "-installsuffix", "testcshared", 368 "-o", libname, "libgo2", 369 ) 370 adbPush(t, libname) 371 372 linkFlags := "-Wl,--no-as-needed" 373 if GOOS == "darwin" { 374 linkFlags = "" 375 } 376 377 runCC(t, "-o", cmd, "main2.c", linkFlags, libname) 378 adbPush(t, cmd) 379 380 defer os.Remove(libname) 381 defer os.Remove(bin) 382 383 out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin) 384 385 if strings.TrimSpace(out) != "PASS" { 386 t.Error(out) 387 } 388 } 389 390 // test3: tests main.main is exported on android. 391 func TestMainExportedOnAndroid(t *testing.T) { 392 t.Parallel() 393 394 switch GOOS { 395 case "android": 396 break 397 default: 398 t.Logf("Skipping on %s", GOOS) 399 return 400 } 401 402 cmd := "testp3" 403 bin := cmdToRun(cmd) 404 405 createHeadersOnce(t) 406 407 runCC(t, "-o", cmd, "main3.c", "-ldl") 408 adbPush(t, cmd) 409 410 defer os.Remove(bin) 411 412 out := runExe(t, nil, bin, "./"+libgoname) 413 if strings.TrimSpace(out) != "PASS" { 414 t.Error(out) 415 } 416 } 417 418 func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) { 419 libname := pkgname + "." + libSuffix 420 run(t, 421 gopathEnv, 422 "go", "build", 423 "-buildmode=c-shared", 424 "-installsuffix", "testcshared", 425 "-o", libname, pkgname, 426 ) 427 adbPush(t, libname) 428 if GOOS != "freebsd" { 429 runCC(t, "-pthread", "-o", cmd, cfile, "-ldl") 430 } else { 431 runCC(t, "-pthread", "-o", cmd, cfile) 432 } 433 adbPush(t, cmd) 434 435 bin := cmdToRun(cmd) 436 437 defer os.Remove(libname) 438 defer os.Remove(bin) 439 defer os.Remove(pkgname + ".h") 440 441 out := runExe(t, nil, bin, "./"+libname) 442 if strings.TrimSpace(out) != "PASS" { 443 t.Error(run(t, nil, bin, libname, "verbose")) 444 } 445 } 446 447 // test4: test signal handlers 448 func TestSignalHandlers(t *testing.T) { 449 t.Parallel() 450 if GOOS == "windows" { 451 t.Logf("Skipping on %s", GOOS) 452 return 453 } 454 testSignalHandlers(t, "libgo4", "main4.c", "testp4") 455 } 456 457 // test5: test signal handlers with os/signal.Notify 458 func TestSignalHandlersWithNotify(t *testing.T) { 459 t.Parallel() 460 if GOOS == "windows" { 461 t.Logf("Skipping on %s", GOOS) 462 return 463 } 464 testSignalHandlers(t, "libgo5", "main5.c", "testp5") 465 } 466 467 func TestPIE(t *testing.T) { 468 t.Parallel() 469 470 switch GOOS { 471 case "linux", "android": 472 break 473 default: 474 t.Logf("Skipping on %s", GOOS) 475 return 476 } 477 478 createHeadersOnce(t) 479 480 f, err := elf.Open(libgoname) 481 if err != nil { 482 t.Fatalf("elf.Open failed: %v", err) 483 } 484 defer f.Close() 485 486 ds := f.SectionByType(elf.SHT_DYNAMIC) 487 if ds == nil { 488 t.Fatalf("no SHT_DYNAMIC section") 489 } 490 d, err := ds.Data() 491 if err != nil { 492 t.Fatalf("can't read SHT_DYNAMIC contents: %v", err) 493 } 494 for len(d) > 0 { 495 var tag elf.DynTag 496 switch f.Class { 497 case elf.ELFCLASS32: 498 tag = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 499 d = d[8:] 500 case elf.ELFCLASS64: 501 tag = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 502 d = d[16:] 503 } 504 if tag == elf.DT_TEXTREL { 505 t.Fatalf("%s has DT_TEXTREL flag", libgoname) 506 } 507 } 508 } 509 510 // Test that installing a second time recreates the header files. 511 func TestCachedInstall(t *testing.T) { 512 tmpdir, err := ioutil.TempDir("", "cshared") 513 if err != nil { 514 t.Fatal(err) 515 } 516 // defer os.RemoveAll(tmpdir) 517 518 copyFile(t, filepath.Join(tmpdir, "src", "libgo", "libgo.go"), filepath.Join("src", "libgo", "libgo.go")) 519 copyFile(t, filepath.Join(tmpdir, "src", "p", "p.go"), filepath.Join("src", "p", "p.go")) 520 521 env := append(os.Environ(), "GOPATH="+tmpdir) 522 523 buildcmd := []string{"go", "install", "-x", "-i", "-buildmode=c-shared", "-installsuffix", "testcshared", "libgo"} 524 525 cmd := exec.Command(buildcmd[0], buildcmd[1:]...) 526 cmd.Env = env 527 t.Log(buildcmd) 528 out, err := cmd.CombinedOutput() 529 t.Logf("%s", out) 530 if err != nil { 531 t.Fatal(err) 532 } 533 534 var libgoh, ph string 535 536 walker := func(path string, info os.FileInfo, err error) error { 537 if err != nil { 538 t.Fatal(err) 539 } 540 var ps *string 541 switch filepath.Base(path) { 542 case "libgo.h": 543 ps = &libgoh 544 case "p.h": 545 ps = &ph 546 } 547 if ps != nil { 548 if *ps != "" { 549 t.Fatalf("%s found again", *ps) 550 } 551 *ps = path 552 } 553 return nil 554 } 555 556 if err := filepath.Walk(tmpdir, walker); err != nil { 557 t.Fatal(err) 558 } 559 560 if libgoh == "" { 561 t.Fatal("libgo.h not installed") 562 } 563 if ph == "" { 564 t.Fatal("p.h not installed") 565 } 566 567 if err := os.Remove(libgoh); err != nil { 568 t.Fatal(err) 569 } 570 if err := os.Remove(ph); err != nil { 571 t.Fatal(err) 572 } 573 574 cmd = exec.Command(buildcmd[0], buildcmd[1:]...) 575 cmd.Env = env 576 t.Log(buildcmd) 577 out, err = cmd.CombinedOutput() 578 t.Logf("%s", out) 579 if err != nil { 580 t.Fatal(err) 581 } 582 583 if _, err := os.Stat(libgoh); err != nil { 584 t.Errorf("libgo.h not installed in second run: %v", err) 585 } 586 if _, err := os.Stat(ph); err != nil { 587 t.Errorf("p.h not installed in second run: %v", err) 588 } 589 } 590 591 // copyFile copies src to dst. 592 func copyFile(t *testing.T, dst, src string) { 593 t.Helper() 594 data, err := ioutil.ReadFile(src) 595 if err != nil { 596 t.Fatal(err) 597 } 598 if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { 599 t.Fatal(err) 600 } 601 if err := ioutil.WriteFile(dst, data, 0666); err != nil { 602 t.Fatal(err) 603 } 604 }