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