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