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