Skip to content

Commit

Permalink
misc/swig: restructure as a driver
Browse files Browse the repository at this point in the history
Currently, the misc/swig tests directly use Swig and C++ and will fail
to build if either Swig or a C++ compiler are not present. Typically,
we hide this fact from users because dist test itself checks for Swig
and a C++ compiler before even attempting to run this test, though
users will see this is they try to go test ./... from misc.

However, we're about to move the misc/swig tests into the cmd module,
where they will be much more visible and much more likely to run
unintentionally. To prevent build errors, this CL restructures these
tests into a single pure Go test plus two test packages hidden in
testdata. This is relatively easy to do for this test because there
are only four test cases total. The pure Go test can check for the
necessary build tools before trying to build and run the tests in
testdata. This also gives us the opportunity to move the LTO variant
of these tests out of dist and into the test itself, simplifying dist.

For golang#37486.

Change-Id: Ibda089b4069e36866cb31867a7006c790be2d8b1
Reviewed-on: https://go-review.googlesource.com/c/go/+/493599
TryBot-Result: Gopher Robot <[email protected]>
Run-TryBot: Austin Clements <[email protected]>
Reviewed-by: Bryan Mills <[email protected]>
  • Loading branch information
aclements committed May 12, 2023
1 parent 711609d commit 2484e13
Show file tree
Hide file tree
Showing 13 changed files with 266 additions and 184 deletions.
11 changes: 0 additions & 11 deletions misc/swig/callback/callback.go

This file was deleted.

33 changes: 0 additions & 33 deletions misc/swig/callback/callback_test.go

This file was deleted.

7 changes: 7 additions & 0 deletions misc/swig/nocgo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file is just to avoid build errors if there's no cgo.

package swig
15 changes: 0 additions & 15 deletions misc/swig/stdio/file.go

This file was deleted.

28 changes: 0 additions & 28 deletions misc/swig/stdio/file_test.go

This file was deleted.

149 changes: 149 additions & 0 deletions misc/swig/swig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build cgo

package swig

import (
"bytes"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"testing"
)

func TestStdio(t *testing.T) {
mustHaveSwig(t)
run(t, "testdata/stdio", false)
}

func TestCall(t *testing.T) {
mustHaveSwig(t)
mustHaveCxx(t)
run(t, "testdata/callback", false, "Call")
t.Run("lto", func(t *testing.T) { run(t, "testdata/callback", true, "Call") })
}

func TestCallback(t *testing.T) {
mustHaveSwig(t)
mustHaveCxx(t)
run(t, "testdata/callback", false, "Callback")
t.Run("lto", func(t *testing.T) { run(t, "testdata/callback", true, "Callback") })
}

func run(t *testing.T, dir string, lto bool, args ...string) {
runArgs := append([]string{"run", "."}, args...)
cmd := exec.Command("go", runArgs...)
cmd.Dir = dir
if lto {
const cflags = "-flto -Wno-lto-type-mismatch -Wno-unknown-warning-option"
cmd.Env = append(cmd.Environ(),
"CGO_CFLAGS="+cflags,
"CGO_CXXFLAGS="+cflags,
"CGO_LDFLAGS="+cflags)
}
out, err := cmd.CombinedOutput()
if string(out) != "OK\n" {
t.Errorf("%s", string(out))
}
if err != nil {
t.Errorf("%s", err)
}
}

func mustHaveCxx(t *testing.T) {
// Ask the go tool for the CXX it's configured to use.
cxx, err := exec.Command("go", "env", "CXX").CombinedOutput()
if err != nil {
t.Fatalf("go env CXX failed: %s", err)
}
cxx = bytes.TrimSuffix(cxx, []byte("\n"))
// TODO(austin): "go env CXX" can return a quoted list. Use quoted.Split.
p, err := exec.LookPath(string(cxx))
if p == "" {
t.Skipf("test requires C++ compiler, but failed to find %s: %s", string(cxx), err)
}
}

var (
swigOnce sync.Once
haveSwig bool
)

func mustHaveSwig(t *testing.T) {
swigOnce.Do(func() {
mustHaveSwigOnce(t)
haveSwig = true
})
// The first call will skip t with a nice message. On later calls, we just skip.
if !haveSwig {
t.Skip("swig not found")
}
}

func mustHaveSwigOnce(t *testing.T) {
swig, err := exec.LookPath("swig")
if err != nil {
t.Skipf("swig not in PATH: %s", err)
}

// Check that swig was installed with Go support by checking
// that a go directory exists inside the swiglib directory.
// See https://golang.org/issue/23469.
output, err := exec.Command(swig, "-go", "-swiglib").Output()
if err != nil {
t.Skip("swig is missing Go support")
}
swigDir := strings.TrimSpace(string(output))

_, err = os.Stat(filepath.Join(swigDir, "go"))
if err != nil {
t.Skip("swig is missing Go support")
}

// Check that swig has a new enough version.
// See https://golang.org/issue/22858.
out, err := exec.Command(swig, "-version").CombinedOutput()
if err != nil {
t.Skipf("failed to get swig version:%s\n%s", err, string(out))
}

re := regexp.MustCompile(`[vV]ersion +(\d+)([.]\d+)?([.]\d+)?`)
matches := re.FindSubmatch(out)
if matches == nil {
// Can't find version number; hope for the best.
t.Logf("failed to find swig version, continuing")
return
}

var parseError error
atoi := func(s string) int {
x, err := strconv.Atoi(s)
if err != nil && parseError == nil {
parseError = err
}
return x
}
var major, minor, patch int
major = atoi(string(matches[1]))
if len(matches[2]) > 0 {
minor = atoi(string(matches[2][1:]))
}
if len(matches[3]) > 0 {
patch = atoi(string(matches[3][1:]))
}
if parseError != nil {
t.Logf("error parsing swig version %q, continuing anyway: %s", string(matches[0]), parseError)
return
}
t.Logf("found swig version %d.%d.%d", major, minor, patch)
if major < 3 || (major == 3 && minor == 0 && patch < 6) {
t.Skip("test requires swig 3.0.6 or later")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// included in the package.

#include <string>
#include "callback.h"
#include "main.h"

std::string Caller::call() {
if (callback_ != 0)
Expand Down
60 changes: 60 additions & 0 deletions misc/swig/testdata/callback/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"fmt"
"os"
)

func main() {
if len(os.Args) != 2 {
fatal("usage: callback testname")
}
switch os.Args[1] {
default:
fatal("unknown test %q", os.Args[1])
case "Call":
testCall()
case "Callback":
testCallback()
}
println("OK")
}

func fatal(f string, args ...any) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(f, args...))
os.Exit(1)
}

type GoCallback struct{}

func (p *GoCallback) Run() string {
return "GoCallback.Run"
}

func testCall() {
c := NewCaller()
cb := NewCallback()

c.SetCallback(cb)
s := c.Call()
if s != "Callback::run" {
fatal("unexpected string from Call: %q", s)
}
c.DelCallback()
}

func testCallback() {
c := NewCaller()
cb := NewDirectorCallback(&GoCallback{})
c.SetCallback(cb)
s := c.Call()
if s != "GoCallback.Run" {
fatal("unexpected string from Call with callback: %q", s)
}
c.DelCallback()
DeleteDirectorCallback(cb)
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

%{
#include <string>
#include "callback.h"
#include "main.h"
%}

%include "std_string.i"

%feature("director");

%include "callback.h"
%include "main.h"
45 changes: 45 additions & 0 deletions misc/swig/testdata/stdio/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file is here just to cause problems.
// main.swig turns into a file also named main.go.
// Make sure cmd/go keeps them separate
// when both are passed to cgo.

package main

//int F(void) { return 1; }
import "C"
import (
"fmt"
"os"
)

func F() int { return int(C.F()) }

func main() {
if x := int(C.F()); x != 1 {
fatal("x = %d, want 1", x)
}

// Open this file itself and verify that the first few characters are
// as expected.
f := Fopen("main.go", "r")
if f.Swigcptr() == 0 {
fatal("fopen failed")
}
if Fgetc(f) != '/' || Fgetc(f) != '/' || Fgetc(f) != ' ' || Fgetc(f) != 'C' {
fatal("read unexpected characters")
}
if Fclose(f) != 0 {
fatal("fclose failed")
}

println("OK")
}

func fatal(f string, args ...any) {
fmt.Fprintln(os.Stderr, fmt.Sprintf(f, args...))
os.Exit(1)
}
File renamed without changes.
Loading

0 comments on commit 2484e13

Please sign in to comment.