1// Copyright 2023 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 5package swig 6 7import ( 8 "cmd/internal/quoted" 9 "internal/testenv" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "regexp" 14 "strconv" 15 "strings" 16 "sync" 17 "testing" 18) 19 20func TestStdio(t *testing.T) { 21 testenv.MustHaveCGO(t) 22 mustHaveSwig(t) 23 run(t, "testdata/stdio", false) 24} 25 26func TestCall(t *testing.T) { 27 testenv.MustHaveCGO(t) 28 mustHaveSwig(t) 29 mustHaveCxx(t) 30 run(t, "testdata/callback", false, "Call") 31 t.Run("lto", func(t *testing.T) { run(t, "testdata/callback", true, "Call") }) 32} 33 34func TestCallback(t *testing.T) { 35 testenv.MustHaveCGO(t) 36 mustHaveSwig(t) 37 mustHaveCxx(t) 38 run(t, "testdata/callback", false, "Callback") 39 t.Run("lto", func(t *testing.T) { run(t, "testdata/callback", true, "Callback") }) 40} 41 42func run(t *testing.T, dir string, lto bool, args ...string) { 43 runArgs := append([]string{"run", "."}, args...) 44 cmd := exec.Command("go", runArgs...) 45 cmd.Dir = dir 46 if lto { 47 // On the builders we're using the default /usr/bin/ld, but 48 // that has problems when asking for LTO in particular. Force 49 // use of lld, which ships with our clang installation. 50 extraLDFlags := "" 51 if strings.Contains(testenv.Builder(), "clang") { 52 extraLDFlags += " -fuse-ld=lld" 53 } 54 const cflags = "-flto -Wno-lto-type-mismatch -Wno-unknown-warning-option" 55 cmd.Env = append(cmd.Environ(), 56 "CGO_CFLAGS="+cflags, 57 "CGO_CXXFLAGS="+cflags, 58 "CGO_LDFLAGS="+cflags+extraLDFlags) 59 } 60 out, err := cmd.CombinedOutput() 61 if string(out) != "OK\n" { 62 t.Errorf("%s", string(out)) 63 } 64 if err != nil { 65 t.Errorf("%s", err) 66 } 67} 68 69func mustHaveCxx(t *testing.T) { 70 // Ask the go tool for the CXX it's configured to use. 71 cxx, err := exec.Command("go", "env", "CXX").CombinedOutput() 72 if err != nil { 73 t.Fatalf("go env CXX failed: %s", err) 74 } 75 args, err := quoted.Split(string(cxx)) 76 if err != nil { 77 t.Skipf("could not parse 'go env CXX' output %q: %s", string(cxx), err) 78 } 79 if len(args) == 0 { 80 t.Skip("no C++ compiler") 81 } 82 testenv.MustHaveExecPath(t, string(args[0])) 83} 84 85var ( 86 swigOnce sync.Once 87 haveSwig bool 88) 89 90func mustHaveSwig(t *testing.T) { 91 swigOnce.Do(func() { 92 mustHaveSwigOnce(t) 93 haveSwig = true 94 }) 95 // The first call will skip t with a nice message. On later calls, we just skip. 96 if !haveSwig { 97 t.Skip("swig not found") 98 } 99} 100 101func mustHaveSwigOnce(t *testing.T) { 102 swig, err := exec.LookPath("swig") 103 if err != nil { 104 t.Skipf("swig not in PATH: %s", err) 105 } 106 107 // Check that swig was installed with Go support by checking 108 // that a go directory exists inside the swiglib directory. 109 // See https://golang.org/issue/23469. 110 output, err := exec.Command(swig, "-go", "-swiglib").Output() 111 if err != nil { 112 t.Skip("swig is missing Go support") 113 } 114 swigDir := strings.TrimSpace(string(output)) 115 116 _, err = os.Stat(filepath.Join(swigDir, "go")) 117 if err != nil { 118 t.Skip("swig is missing Go support") 119 } 120 121 // Check that swig has a new enough version. 122 // See https://golang.org/issue/22858. 123 out, err := exec.Command(swig, "-version").CombinedOutput() 124 if err != nil { 125 t.Skipf("failed to get swig version:%s\n%s", err, string(out)) 126 } 127 128 re := regexp.MustCompile(`[vV]ersion +(\d+)([.]\d+)?([.]\d+)?`) 129 matches := re.FindSubmatch(out) 130 if matches == nil { 131 // Can't find version number; hope for the best. 132 t.Logf("failed to find swig version, continuing") 133 return 134 } 135 136 var parseError error 137 atoi := func(s string) int { 138 x, err := strconv.Atoi(s) 139 if err != nil && parseError == nil { 140 parseError = err 141 } 142 return x 143 } 144 var major, minor, patch int 145 major = atoi(string(matches[1])) 146 if len(matches[2]) > 0 { 147 minor = atoi(string(matches[2][1:])) 148 } 149 if len(matches[3]) > 0 { 150 patch = atoi(string(matches[3][1:])) 151 } 152 if parseError != nil { 153 t.Logf("error parsing swig version %q, continuing anyway: %s", string(matches[0]), parseError) 154 return 155 } 156 t.Logf("found swig version %d.%d.%d", major, minor, patch) 157 if major < 3 || (major == 3 && minor == 0 && patch < 6) { 158 t.Skip("test requires swig 3.0.6 or later") 159 } 160} 161