github.com/golang/gofrontend@v0.0.0-20240429183944-60f985a78526/libgo/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 "bytes" 9 "debug/elf" 10 "debug/pe" 11 "encoding/binary" 12 "flag" 13 "fmt" 14 "log" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "runtime" 19 "strings" 20 "sync" 21 "testing" 22 "unicode" 23 ) 24 25 // C compiler with args (from $(go env CC) $(go env GOGCCFLAGS)). 26 var cc []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 os.Exit(testMain(m)) 37 } 38 39 func testMain(m *testing.M) int { 40 log.SetFlags(log.Lshortfile) 41 flag.Parse() 42 if testing.Short() && os.Getenv("GO_BUILDER_NAME") == "" { 43 fmt.Printf("SKIP - short mode and $GO_BUILDER_NAME not set\n") 44 os.Exit(0) 45 } 46 47 GOOS = goEnv("GOOS") 48 GOARCH = goEnv("GOARCH") 49 GOROOT = goEnv("GOROOT") 50 51 if _, err := os.Stat(GOROOT); os.IsNotExist(err) { 52 log.Fatalf("Unable able to find GOROOT at '%s'", GOROOT) 53 } 54 55 androiddir = fmt.Sprintf("/data/local/tmp/testcshared-%d", os.Getpid()) 56 if runtime.GOOS != GOOS && GOOS == "android" { 57 args := append(adbCmd(), "exec-out", "mkdir", "-p", androiddir) 58 cmd := exec.Command(args[0], args[1:]...) 59 out, err := cmd.CombinedOutput() 60 if err != nil { 61 log.Fatalf("setupAndroid failed: %v\n%s\n", err, out) 62 } 63 defer cleanupAndroid() 64 } 65 66 cc = []string{goEnv("CC")} 67 68 out := goEnv("GOGCCFLAGS") 69 quote := '\000' 70 start := 0 71 lastSpace := true 72 backslash := false 73 s := string(out) 74 for i, c := range s { 75 if quote == '\000' && unicode.IsSpace(c) { 76 if !lastSpace { 77 cc = append(cc, s[start:i]) 78 lastSpace = true 79 } 80 } else { 81 if lastSpace { 82 start = i 83 lastSpace = false 84 } 85 if quote == '\000' && !backslash && (c == '"' || c == '\'') { 86 quote = c 87 backslash = false 88 } else if !backslash && quote == c { 89 quote = '\000' 90 } else if (quote == '\000' || quote == '"') && !backslash && c == '\\' { 91 backslash = true 92 } else { 93 backslash = false 94 } 95 } 96 } 97 if !lastSpace { 98 cc = append(cc, s[start:]) 99 } 100 101 switch GOOS { 102 case "darwin", "ios": 103 // For Darwin/ARM. 104 // TODO(crawshaw): can we do better? 105 cc = append(cc, []string{"-framework", "CoreFoundation", "-framework", "Foundation"}...) 106 case "android": 107 cc = append(cc, "-pie") 108 } 109 libgodir := GOOS + "_" + GOARCH 110 switch GOOS { 111 case "darwin", "ios": 112 if GOARCH == "arm64" { 113 libgodir += "_shared" 114 } 115 case "dragonfly", "freebsd", "linux", "netbsd", "openbsd", "solaris", "illumos": 116 libgodir += "_shared" 117 } 118 cc = append(cc, "-I", filepath.Join("pkg", libgodir)) 119 120 // Force reallocation (and avoid aliasing bugs) for parallel tests that append to cc. 121 cc = cc[:len(cc):len(cc)] 122 123 if GOOS == "windows" { 124 exeSuffix = ".exe" 125 } 126 127 // Copy testdata into GOPATH/src/testcshared, along with a go.mod file 128 // declaring the same path. 129 130 GOPATH, err := os.MkdirTemp("", "cshared_test") 131 if err != nil { 132 log.Panic(err) 133 } 134 defer os.RemoveAll(GOPATH) 135 os.Setenv("GOPATH", GOPATH) 136 137 modRoot := filepath.Join(GOPATH, "src", "testcshared") 138 if err := overlayDir(modRoot, "testdata"); err != nil { 139 log.Panic(err) 140 } 141 if err := os.Chdir(modRoot); err != nil { 142 log.Panic(err) 143 } 144 os.Setenv("PWD", modRoot) 145 if err := os.WriteFile("go.mod", []byte("module testcshared\n"), 0666); err != nil { 146 log.Panic(err) 147 } 148 149 // Directory where cgo headers and outputs will be installed. 150 // The installation directory format varies depending on the platform. 151 output, err := exec.Command("go", "list", 152 "-buildmode=c-shared", 153 "-installsuffix", "testcshared", 154 "-f", "{{.Target}}", 155 "./libgo").CombinedOutput() 156 if err != nil { 157 log.Panicf("go list failed: %v\n%s", err, output) 158 } 159 target := string(bytes.TrimSpace(output)) 160 libgoname = filepath.Base(target) 161 installdir = filepath.Dir(target) 162 libSuffix = strings.TrimPrefix(filepath.Ext(target), ".") 163 164 return m.Run() 165 } 166 167 func goEnv(key string) string { 168 out, err := exec.Command("go", "env", key).Output() 169 if err != nil { 170 log.Printf("go env %s failed:\n%s", key, err) 171 log.Panicf("%s", err.(*exec.ExitError).Stderr) 172 } 173 return strings.TrimSpace(string(out)) 174 } 175 176 func cmdToRun(name string) string { 177 return "./" + name + exeSuffix 178 } 179 180 func adbCmd() []string { 181 cmd := []string{"adb"} 182 if flags := os.Getenv("GOANDROID_ADB_FLAGS"); flags != "" { 183 cmd = append(cmd, strings.Split(flags, " ")...) 184 } 185 return cmd 186 } 187 188 func adbPush(t *testing.T, filename string) { 189 if runtime.GOOS == GOOS || GOOS != "android" { 190 return 191 } 192 args := append(adbCmd(), "push", filename, fmt.Sprintf("%s/%s", androiddir, filename)) 193 cmd := exec.Command(args[0], args[1:]...) 194 if out, err := cmd.CombinedOutput(); err != nil { 195 t.Fatalf("adb command failed: %v\n%s\n", err, out) 196 } 197 } 198 199 func adbRun(t *testing.T, env []string, adbargs ...string) string { 200 if GOOS != "android" { 201 t.Fatalf("trying to run adb command when operating system is not android.") 202 } 203 args := append(adbCmd(), "exec-out") 204 // Propagate LD_LIBRARY_PATH to the adb shell invocation. 205 for _, e := range env { 206 if strings.Contains(e, "LD_LIBRARY_PATH=") { 207 adbargs = append([]string{e}, adbargs...) 208 break 209 } 210 } 211 shellcmd := fmt.Sprintf("cd %s; %s", androiddir, strings.Join(adbargs, " ")) 212 args = append(args, shellcmd) 213 cmd := exec.Command(args[0], args[1:]...) 214 out, err := cmd.CombinedOutput() 215 if err != nil { 216 t.Fatalf("adb command failed: %v\n%s\n", err, out) 217 } 218 return strings.Replace(string(out), "\r", "", -1) 219 } 220 221 func run(t *testing.T, extraEnv []string, args ...string) string { 222 t.Helper() 223 cmd := exec.Command(args[0], args[1:]...) 224 if len(extraEnv) > 0 { 225 cmd.Env = append(os.Environ(), extraEnv...) 226 } 227 228 if GOOS != "windows" { 229 // TestUnexportedSymbols relies on file descriptor 30 230 // being closed when the program starts, so enforce 231 // that in all cases. (The first three descriptors are 232 // stdin/stdout/stderr, so we just need to make sure 233 // that cmd.ExtraFiles[27] exists and is nil.) 234 cmd.ExtraFiles = make([]*os.File, 28) 235 } 236 237 out, err := cmd.CombinedOutput() 238 if err != nil { 239 t.Fatalf("command failed: %v\n%v\n%s\n", args, err, out) 240 } else { 241 t.Logf("run: %v", args) 242 } 243 return string(out) 244 } 245 246 func runExe(t *testing.T, extraEnv []string, args ...string) string { 247 t.Helper() 248 if runtime.GOOS != GOOS && GOOS == "android" { 249 return adbRun(t, append(os.Environ(), extraEnv...), args...) 250 } 251 return run(t, extraEnv, args...) 252 } 253 254 func runCC(t *testing.T, args ...string) string { 255 t.Helper() 256 // This function is run in parallel, so append to a copy of cc 257 // rather than cc itself. 258 return run(t, nil, append(append([]string(nil), cc...), args...)...) 259 } 260 261 func createHeaders() error { 262 // The 'cgo' command generates a number of additional artifacts, 263 // but we're only interested in the header. 264 // Shunt the rest of the outputs to a temporary directory. 265 objDir, err := os.MkdirTemp("", "testcshared_obj") 266 if err != nil { 267 return err 268 } 269 defer os.RemoveAll(objDir) 270 271 // Generate a C header file for p, which is a non-main dependency 272 // of main package libgo. 273 // 274 // TODO(golang.org/issue/35715): This should be simpler. 275 args := []string{"go", "tool", "cgo", 276 "-objdir", objDir, 277 "-exportheader", "p.h", 278 filepath.Join(".", "p", "p.go")} 279 cmd := exec.Command(args[0], args[1:]...) 280 out, err := cmd.CombinedOutput() 281 if err != nil { 282 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 283 } 284 285 // Generate a C header file for libgo itself. 286 args = []string{"go", "install", "-buildmode=c-shared", 287 "-installsuffix", "testcshared", "./libgo"} 288 cmd = exec.Command(args[0], args[1:]...) 289 out, err = cmd.CombinedOutput() 290 if err != nil { 291 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 292 } 293 294 args = []string{"go", "build", "-buildmode=c-shared", 295 "-installsuffix", "testcshared", 296 "-o", libgoname, 297 filepath.Join(".", "libgo", "libgo.go")} 298 if GOOS == "windows" && strings.HasSuffix(args[6], ".a") { 299 args[6] = strings.TrimSuffix(args[6], ".a") + ".dll" 300 } 301 cmd = exec.Command(args[0], args[1:]...) 302 out, err = cmd.CombinedOutput() 303 if err != nil { 304 return fmt.Errorf("command failed: %v\n%v\n%s\n", args, err, out) 305 } 306 if GOOS == "windows" { 307 // We can't simply pass -Wl,--out-implib, because this relies on having imports from multiple packages, 308 // which results in the linkers output implib getting overwritten at each step. So instead build the 309 // import library the traditional way, using a def file. 310 err = os.WriteFile("libgo.def", 311 []byte("LIBRARY libgo.dll\nEXPORTS\n\tDidInitRun\n\tDidMainRun\n\tDivu\n\tFromPkg\n\t_cgo_dummy_export\n"), 312 0644) 313 if err != nil { 314 return fmt.Errorf("unable to write def file: %v", err) 315 } 316 out, err = exec.Command(cc[0], append(cc[1:], "-print-prog-name=dlltool")...).CombinedOutput() 317 if err != nil { 318 return fmt.Errorf("unable to find dlltool path: %v\n%s\n", err, out) 319 } 320 args := []string{strings.TrimSpace(string(out)), "-D", args[6], "-l", libgoname, "-d", "libgo.def"} 321 322 // This is an unfortunate workaround for https://github.com/mstorsjo/llvm-mingw/issues/205 in which 323 // we basically reimplement the contents of the dlltool.sh wrapper: https://git.io/JZFlU 324 dlltoolContents, err := os.ReadFile(args[0]) 325 if err != nil { 326 return fmt.Errorf("unable to read dlltool: %v\n", err) 327 } 328 if bytes.HasPrefix(dlltoolContents, []byte("#!/bin/sh")) && bytes.Contains(dlltoolContents, []byte("llvm-dlltool")) { 329 base, name := filepath.Split(args[0]) 330 args[0] = filepath.Join(base, "llvm-dlltool") 331 var machine string 332 switch prefix, _, _ := strings.Cut(name, "-"); prefix { 333 case "i686": 334 machine = "i386" 335 case "x86_64": 336 machine = "i386:x86-64" 337 case "armv7": 338 machine = "arm" 339 case "aarch64": 340 machine = "arm64" 341 } 342 if len(machine) > 0 { 343 args = append(args, "-m", machine) 344 } 345 } 346 347 out, err = exec.Command(args[0], args[1:]...).CombinedOutput() 348 if err != nil { 349 return fmt.Errorf("unable to run dlltool to create import library: %v\n%s\n", err, out) 350 } 351 } 352 353 if runtime.GOOS != GOOS && GOOS == "android" { 354 args = append(adbCmd(), "push", libgoname, fmt.Sprintf("%s/%s", androiddir, libgoname)) 355 cmd = exec.Command(args[0], args[1:]...) 356 out, err = cmd.CombinedOutput() 357 if err != nil { 358 return fmt.Errorf("adb command failed: %v\n%s\n", err, out) 359 } 360 } 361 362 return nil 363 } 364 365 var ( 366 headersOnce sync.Once 367 headersErr error 368 ) 369 370 func createHeadersOnce(t *testing.T) { 371 headersOnce.Do(func() { 372 headersErr = createHeaders() 373 }) 374 if headersErr != nil { 375 t.Fatal(headersErr) 376 } 377 } 378 379 func cleanupAndroid() { 380 if GOOS != "android" { 381 return 382 } 383 args := append(adbCmd(), "exec-out", "rm", "-rf", androiddir) 384 cmd := exec.Command(args[0], args[1:]...) 385 out, err := cmd.CombinedOutput() 386 if err != nil { 387 log.Panicf("cleanupAndroid failed: %v\n%s\n", err, out) 388 } 389 } 390 391 // test0: exported symbols in shared lib are accessible. 392 func TestExportedSymbols(t *testing.T) { 393 t.Parallel() 394 395 cmd := "testp0" 396 bin := cmdToRun(cmd) 397 398 createHeadersOnce(t) 399 400 runCC(t, "-I", installdir, "-o", cmd, "main0.c", libgoname) 401 adbPush(t, cmd) 402 403 defer os.Remove(bin) 404 405 out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin) 406 if strings.TrimSpace(out) != "PASS" { 407 t.Error(out) 408 } 409 } 410 411 func checkNumberOfExportedFunctionsWindows(t *testing.T, exportAllSymbols bool) { 412 const prog = ` 413 package main 414 415 import "C" 416 417 //export GoFunc 418 func GoFunc() { 419 println(42) 420 } 421 422 //export GoFunc2 423 func GoFunc2() { 424 println(24) 425 } 426 427 func main() { 428 } 429 ` 430 431 tmpdir := t.TempDir() 432 433 srcfile := filepath.Join(tmpdir, "test.go") 434 objfile := filepath.Join(tmpdir, "test.dll") 435 if err := os.WriteFile(srcfile, []byte(prog), 0666); err != nil { 436 t.Fatal(err) 437 } 438 argv := []string{"build", "-buildmode=c-shared"} 439 if exportAllSymbols { 440 argv = append(argv, "-ldflags", "-extldflags=-Wl,--export-all-symbols") 441 } 442 argv = append(argv, "-o", objfile, srcfile) 443 out, err := exec.Command("go", argv...).CombinedOutput() 444 if err != nil { 445 t.Fatalf("build failure: %s\n%s\n", err, string(out)) 446 } 447 448 f, err := pe.Open(objfile) 449 if err != nil { 450 t.Fatalf("pe.Open failed: %v", err) 451 } 452 defer f.Close() 453 section := f.Section(".edata") 454 if section == nil { 455 t.Skip(".edata section is not present") 456 } 457 458 // TODO: deduplicate this struct from cmd/link/internal/ld/pe.go 459 type IMAGE_EXPORT_DIRECTORY struct { 460 _ [2]uint32 461 _ [2]uint16 462 _ [2]uint32 463 NumberOfFunctions uint32 464 NumberOfNames uint32 465 _ [3]uint32 466 } 467 var e IMAGE_EXPORT_DIRECTORY 468 if err := binary.Read(section.Open(), binary.LittleEndian, &e); err != nil { 469 t.Fatalf("binary.Read failed: %v", err) 470 } 471 472 // Only the two exported functions and _cgo_dummy_export should be exported 473 expectedNumber := uint32(3) 474 475 if exportAllSymbols { 476 if e.NumberOfFunctions <= expectedNumber { 477 t.Fatalf("missing exported functions: %v", e.NumberOfFunctions) 478 } 479 if e.NumberOfNames <= expectedNumber { 480 t.Fatalf("missing exported names: %v", e.NumberOfNames) 481 } 482 } else { 483 if e.NumberOfFunctions != expectedNumber { 484 t.Fatalf("got %d exported functions; want %d", e.NumberOfFunctions, expectedNumber) 485 } 486 if e.NumberOfNames != expectedNumber { 487 t.Fatalf("got %d exported names; want %d", e.NumberOfNames, expectedNumber) 488 } 489 } 490 } 491 492 func TestNumberOfExportedFunctions(t *testing.T) { 493 if GOOS != "windows" { 494 t.Skip("skipping windows only test") 495 } 496 t.Parallel() 497 498 t.Run("OnlyExported", func(t *testing.T) { 499 checkNumberOfExportedFunctionsWindows(t, false) 500 }) 501 t.Run("All", func(t *testing.T) { 502 checkNumberOfExportedFunctionsWindows(t, true) 503 }) 504 } 505 506 // test1: shared library can be dynamically loaded and exported symbols are accessible. 507 func TestExportedSymbolsWithDynamicLoad(t *testing.T) { 508 t.Parallel() 509 510 if GOOS == "windows" { 511 t.Logf("Skipping on %s", GOOS) 512 return 513 } 514 515 cmd := "testp1" 516 bin := cmdToRun(cmd) 517 518 createHeadersOnce(t) 519 520 if GOOS != "freebsd" { 521 runCC(t, "-o", cmd, "main1.c", "-ldl") 522 } else { 523 runCC(t, "-o", cmd, "main1.c") 524 } 525 adbPush(t, cmd) 526 527 defer os.Remove(bin) 528 529 out := runExe(t, nil, bin, "./"+libgoname) 530 if strings.TrimSpace(out) != "PASS" { 531 t.Error(out) 532 } 533 } 534 535 // test2: tests libgo2 which does not export any functions. 536 func TestUnexportedSymbols(t *testing.T) { 537 t.Parallel() 538 539 if GOOS == "windows" { 540 t.Logf("Skipping on %s", GOOS) 541 return 542 } 543 544 cmd := "testp2" 545 bin := cmdToRun(cmd) 546 libname := "libgo2." + libSuffix 547 548 run(t, 549 nil, 550 "go", "build", 551 "-buildmode=c-shared", 552 "-installsuffix", "testcshared", 553 "-o", libname, "./libgo2", 554 ) 555 adbPush(t, libname) 556 557 linkFlags := "-Wl,--no-as-needed" 558 if GOOS == "darwin" || GOOS == "ios" { 559 linkFlags = "" 560 } 561 562 runCC(t, "-o", cmd, "main2.c", linkFlags, libname) 563 adbPush(t, cmd) 564 565 defer os.Remove(libname) 566 defer os.Remove(bin) 567 568 out := runExe(t, []string{"LD_LIBRARY_PATH=."}, bin) 569 570 if strings.TrimSpace(out) != "PASS" { 571 t.Error(out) 572 } 573 } 574 575 // test3: tests main.main is exported on android. 576 func TestMainExportedOnAndroid(t *testing.T) { 577 t.Parallel() 578 579 switch GOOS { 580 case "android": 581 break 582 default: 583 t.Logf("Skipping on %s", GOOS) 584 return 585 } 586 587 cmd := "testp3" 588 bin := cmdToRun(cmd) 589 590 createHeadersOnce(t) 591 592 runCC(t, "-o", cmd, "main3.c", "-ldl") 593 adbPush(t, cmd) 594 595 defer os.Remove(bin) 596 597 out := runExe(t, nil, bin, "./"+libgoname) 598 if strings.TrimSpace(out) != "PASS" { 599 t.Error(out) 600 } 601 } 602 603 func testSignalHandlers(t *testing.T, pkgname, cfile, cmd string) { 604 libname := pkgname + "." + libSuffix 605 run(t, 606 nil, 607 "go", "build", 608 "-buildmode=c-shared", 609 "-installsuffix", "testcshared", 610 "-o", libname, pkgname, 611 ) 612 adbPush(t, libname) 613 if GOOS != "freebsd" { 614 runCC(t, "-pthread", "-o", cmd, cfile, "-ldl") 615 } else { 616 runCC(t, "-pthread", "-o", cmd, cfile) 617 } 618 adbPush(t, cmd) 619 620 bin := cmdToRun(cmd) 621 622 defer os.Remove(libname) 623 defer os.Remove(bin) 624 defer os.Remove(pkgname + ".h") 625 626 out := runExe(t, nil, bin, "./"+libname) 627 if strings.TrimSpace(out) != "PASS" { 628 t.Error(run(t, nil, bin, libname, "verbose")) 629 } 630 } 631 632 // test4: test signal handlers 633 func TestSignalHandlers(t *testing.T) { 634 t.Parallel() 635 if GOOS == "windows" { 636 t.Logf("Skipping on %s", GOOS) 637 return 638 } 639 testSignalHandlers(t, "./libgo4", "main4.c", "testp4") 640 } 641 642 // test5: test signal handlers with os/signal.Notify 643 func TestSignalHandlersWithNotify(t *testing.T) { 644 t.Parallel() 645 if GOOS == "windows" { 646 t.Logf("Skipping on %s", GOOS) 647 return 648 } 649 testSignalHandlers(t, "./libgo5", "main5.c", "testp5") 650 } 651 652 func TestPIE(t *testing.T) { 653 t.Parallel() 654 655 switch GOOS { 656 case "linux", "android": 657 break 658 default: 659 t.Logf("Skipping on %s", GOOS) 660 return 661 } 662 663 createHeadersOnce(t) 664 665 f, err := elf.Open(libgoname) 666 if err != nil { 667 t.Fatalf("elf.Open failed: %v", err) 668 } 669 defer f.Close() 670 671 ds := f.SectionByType(elf.SHT_DYNAMIC) 672 if ds == nil { 673 t.Fatalf("no SHT_DYNAMIC section") 674 } 675 d, err := ds.Data() 676 if err != nil { 677 t.Fatalf("can't read SHT_DYNAMIC contents: %v", err) 678 } 679 for len(d) > 0 { 680 var tag elf.DynTag 681 switch f.Class { 682 case elf.ELFCLASS32: 683 tag = elf.DynTag(f.ByteOrder.Uint32(d[:4])) 684 d = d[8:] 685 case elf.ELFCLASS64: 686 tag = elf.DynTag(f.ByteOrder.Uint64(d[:8])) 687 d = d[16:] 688 } 689 if tag == elf.DT_TEXTREL { 690 t.Fatalf("%s has DT_TEXTREL flag", libgoname) 691 } 692 } 693 } 694 695 // Test that installing a second time recreates the header file. 696 func TestCachedInstall(t *testing.T) { 697 tmpdir, err := os.MkdirTemp("", "cshared") 698 if err != nil { 699 t.Fatal(err) 700 } 701 defer os.RemoveAll(tmpdir) 702 703 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "go.mod"), "go.mod") 704 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "libgo", "libgo.go"), filepath.Join("libgo", "libgo.go")) 705 copyFile(t, filepath.Join(tmpdir, "src", "testcshared", "p", "p.go"), filepath.Join("p", "p.go")) 706 707 env := append(os.Environ(), "GOPATH="+tmpdir, "GOBIN="+filepath.Join(tmpdir, "bin")) 708 709 buildcmd := []string{"go", "install", "-x", "-buildmode=c-shared", "-installsuffix", "testcshared", "./libgo"} 710 711 cmd := exec.Command(buildcmd[0], buildcmd[1:]...) 712 cmd.Dir = filepath.Join(tmpdir, "src", "testcshared") 713 cmd.Env = env 714 t.Log(buildcmd) 715 out, err := cmd.CombinedOutput() 716 t.Logf("%s", out) 717 if err != nil { 718 t.Fatal(err) 719 } 720 721 var libgoh, ph string 722 723 walker := func(path string, info os.FileInfo, err error) error { 724 if err != nil { 725 t.Fatal(err) 726 } 727 var ps *string 728 switch filepath.Base(path) { 729 case "libgo.h": 730 ps = &libgoh 731 case "p.h": 732 ps = &ph 733 } 734 if ps != nil { 735 if *ps != "" { 736 t.Fatalf("%s found again", *ps) 737 } 738 *ps = path 739 } 740 return nil 741 } 742 743 if err := filepath.Walk(tmpdir, walker); err != nil { 744 t.Fatal(err) 745 } 746 747 if libgoh == "" { 748 t.Fatal("libgo.h not installed") 749 } 750 751 if err := os.Remove(libgoh); err != nil { 752 t.Fatal(err) 753 } 754 755 cmd = exec.Command(buildcmd[0], buildcmd[1:]...) 756 cmd.Dir = filepath.Join(tmpdir, "src", "testcshared") 757 cmd.Env = env 758 t.Log(buildcmd) 759 out, err = cmd.CombinedOutput() 760 t.Logf("%s", out) 761 if err != nil { 762 t.Fatal(err) 763 } 764 765 if _, err := os.Stat(libgoh); err != nil { 766 t.Errorf("libgo.h not installed in second run: %v", err) 767 } 768 } 769 770 // copyFile copies src to dst. 771 func copyFile(t *testing.T, dst, src string) { 772 t.Helper() 773 data, err := os.ReadFile(src) 774 if err != nil { 775 t.Fatal(err) 776 } 777 if err := os.MkdirAll(filepath.Dir(dst), 0777); err != nil { 778 t.Fatal(err) 779 } 780 if err := os.WriteFile(dst, data, 0666); err != nil { 781 t.Fatal(err) 782 } 783 } 784 785 func TestGo2C2Go(t *testing.T) { 786 switch GOOS { 787 case "darwin", "ios", "windows": 788 // Non-ELF shared libraries don't support the multiple 789 // copies of the runtime package implied by this test. 790 t.Skipf("linking c-shared into Go programs not supported on %s; issue 29061, 49457", GOOS) 791 case "android": 792 t.Skip("test fails on android; issue 29087") 793 } 794 795 t.Parallel() 796 797 tmpdir, err := os.MkdirTemp("", "cshared-TestGo2C2Go") 798 if err != nil { 799 t.Fatal(err) 800 } 801 defer os.RemoveAll(tmpdir) 802 803 lib := filepath.Join(tmpdir, "libtestgo2c2go."+libSuffix) 804 var env []string 805 if GOOS == "windows" && strings.HasSuffix(lib, ".a") { 806 env = append(env, "CGO_LDFLAGS=-Wl,--out-implib,"+lib, "CGO_LDFLAGS_ALLOW=.*") 807 lib = strings.TrimSuffix(lib, ".a") + ".dll" 808 } 809 run(t, env, "go", "build", "-buildmode=c-shared", "-o", lib, "./go2c2go/go") 810 811 cgoCflags := os.Getenv("CGO_CFLAGS") 812 if cgoCflags != "" { 813 cgoCflags += " " 814 } 815 cgoCflags += "-I" + tmpdir 816 817 cgoLdflags := os.Getenv("CGO_LDFLAGS") 818 if cgoLdflags != "" { 819 cgoLdflags += " " 820 } 821 cgoLdflags += "-L" + tmpdir + " -ltestgo2c2go" 822 823 goenv := []string{"CGO_CFLAGS=" + cgoCflags, "CGO_LDFLAGS=" + cgoLdflags} 824 825 ldLibPath := os.Getenv("LD_LIBRARY_PATH") 826 if ldLibPath != "" { 827 ldLibPath += ":" 828 } 829 ldLibPath += tmpdir 830 831 runenv := []string{"LD_LIBRARY_PATH=" + ldLibPath} 832 833 bin := filepath.Join(tmpdir, "m1") + exeSuffix 834 run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m1") 835 runExe(t, runenv, bin) 836 837 bin = filepath.Join(tmpdir, "m2") + exeSuffix 838 run(t, goenv, "go", "build", "-o", bin, "./go2c2go/m2") 839 runExe(t, runenv, bin) 840 }