github.com/corona10/go@v0.0.0-20180224231303-7a218942be57/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 out, err := cmd.CombinedOutput() 205 if err != nil { 206 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out) 207 } else { 208 t.Logf("run: %v", args) 209 } 210 return string(out) 211 } 212 213 func runExe(t *testing.T, env []string, args ...string) string { 214 t.Helper() 215 if GOOS == "android" { 216 return adbRun(t, env, args...) 217 } 218 return run(t, env, args...) 219 } 220 221 func runCC(t *testing.T, args ...string) string { 222 t.Helper() 223 // This function is run in parallel, so append to a copy of cc 224 // rather than cc itself. 225 return run(t, nil, append(append([]string(nil), cc...), args...)...) 226 } 227 228 func createHeaders() error { 229 args := []string{"go", "install", "-i", "-buildmode=c-shared", 230 "-installsuffix", "testcshared", "libgo"} 231 cmd := exec.Command(args[0], args[1:]...) 232 cmd.Env = gopathEnv 233 out, err := cmd.CombinedOutput() 234 if err != nil { 235 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 236 } 237 238 args = []string{"go", "build", "-buildmode=c-shared", 239 "-installsuffix", "testcshared", 240 "-o", libgoname, 241 filepath.Join("src", "libgo", "libgo.go")} 242 cmd = exec.Command(args[0], args[1:]...) 243 cmd.Env = gopathEnv 244 out, err = cmd.CombinedOutput() 245 if err != nil { 246 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 247 } 248 249 if GOOS == "android" { 250 args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname)) 251 cmd = exec.Command(args[0], args[1:]...) 252 out, err = cmd.CombinedOutput() 253 if err != nil { 254 return fmt.Errorf("adb command failed: %v\n%s\n", err, out) 255 } 256 } 257 258 return nil 259 } 260 261 var ( 262 headersOnce sync.Once 263 headersErr error 264 ) 265 266 func createHeadersOnce(t *testing.T) { 267 headersOnce.Do(func() { 268 headersErr = createHeaders() 269 }) 270 if headersErr != nil { 271 t.Fatal(headersErr) 272 } 273 } 274 275 func cleanupHeaders() { 276 os.Remove("libgo.h") 277 } 278 279 func cleanupAndroid() { 280 if GOOS != "android" { 281 return 282 } 283 args := append(adbCmd(), "shell", "rm", "-rf", androiddir) 284 cmd := exec.Command(args[0], args[1:]...) 285 out, err := cmd.CombinedOutput() 286 if err != nil { 287 log.Fatalf("cleanupAndroid failed: %v\n%s\n", err, out) 288 } 289 } 290 291 // test0: exported symbols in shared lib are accessible. 292 func TestExportedSymbols(t *testing.T) { 293 t.Parallel() 294 295 cmd := "testp0" 296 bin := cmdToRun(cmd) 297 298 createHeadersOnce(t) 299 300 runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname) 301 adbPush(t, cmd) 302 303 defer os.Remove(bin) 304 305 out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin) 306 if strings.TrimSpace(out) != "PASS" { 307 t.Error(out) 308 } 309 } 310 311 // test1: shared library can be dynamically loaded and exported symbols are accessible. 312 func TestExportedSymbolsWithDynamicLoad(t *testing.T) { 313 t.Parallel() 314 315 if GOOS == "windows" { 316 t.Logf("Skipping on %s", GOOS) 317 return 318 } 319 320 cmd := "testp1" 321 bin := cmdToRun(cmd) 322 323 createHeadersOnce(t) 324 325 runCC(t, "-o", cmd, "main1.c", "-ldl") 326 adbPush(t, cmd) 327 328 defer os.Remove(bin) 329 330 out := runExe(t, nil, bin, "./"+libgoname) 331 if strings.TrimSpace(out) != "PASS" { 332 t.Error(out) 333 } 334 } 335 336 // test2: tests libgo2 which does not export any functions. 337 func TestUnexportedSymbols(t *testing.T) { 338 t.Parallel() 339 340 if GOOS == "windows" { 341 t.Logf("Skipping on %s", GOOS) 342 return 343 } 344 345 cmd := "testp2" 346 bin := cmdToRun(cmd) 347 libname := "libgo2." + libSuffix 348 349 run(t, 350 gopathEnv, 351 "go", "build", 352 "-buildmode=c-shared", 353 "-installsuffix", "testcshared", 354 "-o", libname, "libgo2", 355 ) 356 adbPush(t, libname) 357 358 linkFlags := "-Wl,--no-as-needed" 359 if GOOS == "darwin" { 360 linkFlags = "" 361 } 362 363 runCC(t, "-o", cmd, "main2.c", linkFlags, libname) 364 adbPush(t, cmd) 365 366 defer os.Remove(libname) 367 defer os.Remove(bin) 368 369 out := runExe(t, append(gopathEnv, "LD_LIBRARY_PATH=."), bin) 370 371 if strings.TrimSpace(out) != "PASS" { 372 t.Error(out) 373 } 374 } 375 376 // test3: tests main.main is exported on android. 377 func TestMainExportedOnAndroid(t *testing.T) { 378 t.Parallel() 379 380 switch GOOS { 381 case "android": 382 break 383 default: 384 t.Logf("Skipping on %s", GOOS) 385 return 386 } 387 388 cmd := "testp3" 389 bin := cmdToRun(cmd) 390 391 createHeadersOnce(t) 392 393 runCC(t, "-o", cmd, "main3.c", "-ldl") 394 adbPush(t, cmd) 395 396 defer os.Remove(bin) 397 398 out := runExe(t, nil, bin, "./"+libgoname) 399 if strings.TrimSpace(out) != "PASS" { 400 t.Error(out) 401 } 402 } 403 404 func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) { 405 libname := pkgname + "." + libSuffix 406 run(t, 407 gopathEnv, 408 "go", "build", 409 "-buildmode=c-shared", 410 "-installsuffix", "testcshared", 411 "-o", libname, pkgname, 412 ) 413 adbPush(t, libname) 414 runCC(t, "-pthread", "-o", cmd, cfile, "-ldl") 415 adbPush(t, cmd) 416 417 bin := cmdToRun(cmd) 418 419 defer os.Remove(libname) 420 defer os.Remove(bin) 421 defer os.Remove(pkgname + ".h") 422 423 out := runExe(t, nil, bin, "./"+libname) 424 if strings.TrimSpace(out) != "PASS" { 425 t.Error(run(t, nil, bin, libname, "verbose")) 426 } 427 } 428 429 // test4: test signal handlers 430 func TestSignalHandlers(t *testing.T) { 431 t.Parallel() 432 if GOOS == "windows" { 433 t.Logf("Skipping on %s", GOOS) 434 return 435 } 436 testSignalHandlers(t, "libgo4", "main4.c", "testp4") 437 } 438 439 // test5: test signal handlers with os/signal.Notify 440 func TestSignalHandlersWithNotify(t *testing.T) { 441 t.Parallel() 442 if GOOS == "windows" { 443 t.Logf("Skipping on %s", GOOS) 444 return 445 } 446 testSignalHandlers(t, "libgo5", "main5.c", "testp5") 447 } 448 449 func TestPIE(t *testing.T) { 450 t.Parallel() 451 452 switch GOOS { 453 case "linux", "android": 454 break 455 default: 456 t.Logf("Skipping on %s", GOOS) 457 return 458 } 459 460 createHeadersOnce(t) 461 462 f, err := elf.Open(libgoname) 463 if err != nil { 464 t.Fatalf("elf.Open failed: %v", err) 465 } 466 defer f.Close() 467 468 ds := f.SectionByType(elf.SHT_DYNAMIC) 469 if ds == nil { 470 t.Fatalf("no SHT_DYNAMIC section") 471 } 472 d, err := ds.Data() 473 if err != nil { 474 t.Fatalf("can't read SHT_DYNAMIC contents: %v", err) 475 } 476 for len(d) > 0 { 477 var tag elf.DynTag 478 switch f.Class { 479 case elf.ELFCLASS32: 480 tag = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 481 d = d[8:] 482 case elf.ELFCLASS64: 483 tag = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 484 d = d[16:] 485 } 486 if tag == elf.DT_TEXTREL { 487 t.Fatalf("%s has DT_TEXTREL flag", libgoname) 488 } 489 } 490 } 491 492 // Test that installing a second time recreates the header files. 493 func TestCachedInstall(t *testing.T) { 494 tmpdir, err := ioutil.TempDir("", "cshared") 495 if err != nil { 496 t.Fatal(err) 497 } 498 // defer os.RemoveAll(tmpdir) 499 500 copyFile(t, filepath.Join(tmpdir, "src", "libgo", "libgo.go"), filepath.Join("src", "libgo", "libgo.go")) 501 copyFile(t, filepath.Join(tmpdir, "src", "p", "p.go"), filepath.Join("src", "p", "p.go")) 502 503 env := append(os.Environ(), "GOPATH="+tmpdir) 504 505 buildcmd := []string{"go", "install", "-x", "-i", "-buildmode=c-shared", "-installsuffix", "testcshared", "libgo"} 506 507 cmd := exec.Command(buildcmd[0], buildcmd[1:]...) 508 cmd.Env = env 509 t.Log(buildcmd) 510 out, err := cmd.CombinedOutput() 511 t.Logf("%s", out) 512 if err != nil { 513 t.Fatal(err) 514 } 515 516 var libgoh, ph string 517 518 walker := func(path string, info os.FileInfo, err error) error { 519 if err != nil { 520 t.Fatal(err) 521 } 522 var ps *string 523 switch filepath.Base(path) { 524 case "libgo.h": 525 ps = &libgoh 526 case "p.h": 527 ps = &ph 528 } 529 if ps != nil { 530 if *ps != "" { 531 t.Fatalf("%s found again", *ps) 532 } 533 *ps = path 534 } 535 return nil 536 } 537 538 if err := filepath.Walk(tmpdir, walker); err != nil { 539 t.Fatal(err) 540 } 541 542 if libgoh == "" { 543 t.Fatal("libgo.h not installed") 544 } 545 if ph == "" { 546 t.Fatal("p.h not installed") 547 } 548 549 if err := os.Remove(libgoh); err != nil { 550 t.Fatal(err) 551 } 552 if err := os.Remove(ph); err != nil { 553 t.Fatal(err) 554 } 555 556 cmd = exec.Command(buildcmd[0], buildcmd[1:]...) 557 cmd.Env = env 558 t.Log(buildcmd) 559 out, err = cmd.CombinedOutput() 560 t.Logf("%s", out) 561 if err != nil { 562 t.Fatal(err) 563 } 564 565 if _, err := os.Stat(libgoh); err != nil { 566 t.Errorf("libgo.h not installed in second run: %v", err) 567 } 568 if _, err := os.Stat(ph); err != nil { 569 t.Errorf("p.h not installed in second run: %v", err) 570 } 571 } 572 573 // copyFile copies src to dst. 574 func copyFile(t *testing.T, dst, src string) { 575 t.Helper() 576 data, err := ioutil.ReadFile(src) 577 if err != nil { 578 t.Fatal(err) 579 } 580 if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { 581 t.Fatal(err) 582 } 583 if err := ioutil.WriteFile(dst, data, 0666); err != nil { 584 t.Fatal(err) 585 } 586 }