• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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