From 2484e1331a6054ffa25e2e6a4146943251171c56 Mon Sep 17 00:00:00 2001 From: Austin Clements Date: Mon, 8 May 2023 14:39:57 -0400 Subject: misc/swig: restructure as a driver 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 #37486. Change-Id: Ibda089b4069e36866cb31867a7006c790be2d8b1 Reviewed-on: https://go-review.googlesource.com/c/go/+/493599 TryBot-Result: Gopher Robot Run-TryBot: Austin Clements Reviewed-by: Bryan Mills --- misc/swig/callback/callback.cc | 15 ---- misc/swig/callback/callback.go | 11 --- misc/swig/callback/callback.h | 20 ----- misc/swig/callback/callback.swigcxx | 18 ---- misc/swig/callback/callback_test.go | 33 ------- misc/swig/nocgo_test.go | 7 ++ misc/swig/stdio/file.go | 15 ---- misc/swig/stdio/file.swig | 24 ----- misc/swig/stdio/file_test.go | 28 ------ misc/swig/swig_test.go | 149 +++++++++++++++++++++++++++++++ misc/swig/testdata/callback/main.cc | 15 ++++ misc/swig/testdata/callback/main.go | 60 +++++++++++++ misc/swig/testdata/callback/main.h | 20 +++++ misc/swig/testdata/callback/main.swigcxx | 18 ++++ misc/swig/testdata/stdio/main.go | 45 ++++++++++ misc/swig/testdata/stdio/main.swig | 24 +++++ 16 files changed, 338 insertions(+), 164 deletions(-) delete mode 100644 misc/swig/callback/callback.cc delete mode 100644 misc/swig/callback/callback.go delete mode 100644 misc/swig/callback/callback.h delete mode 100644 misc/swig/callback/callback.swigcxx delete mode 100644 misc/swig/callback/callback_test.go create mode 100644 misc/swig/nocgo_test.go delete mode 100644 misc/swig/stdio/file.go delete mode 100644 misc/swig/stdio/file.swig delete mode 100644 misc/swig/stdio/file_test.go create mode 100644 misc/swig/swig_test.go create mode 100644 misc/swig/testdata/callback/main.cc create mode 100644 misc/swig/testdata/callback/main.go create mode 100644 misc/swig/testdata/callback/main.h create mode 100644 misc/swig/testdata/callback/main.swigcxx create mode 100644 misc/swig/testdata/stdio/main.go create mode 100644 misc/swig/testdata/stdio/main.swig (limited to 'misc') diff --git a/misc/swig/callback/callback.cc b/misc/swig/callback/callback.cc deleted file mode 100644 index 88bd49c57f..0000000000 --- a/misc/swig/callback/callback.cc +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2013 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 .cc file will be automatically compiled by the go tool and -// included in the package. - -#include -#include "callback.h" - -std::string Caller::call() { - if (callback_ != 0) - return callback_->run(); - return ""; -} diff --git a/misc/swig/callback/callback.go b/misc/swig/callback/callback.go deleted file mode 100644 index 0d6e97f05b..0000000000 --- a/misc/swig/callback/callback.go +++ /dev/null @@ -1,11 +0,0 @@ -// 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 callback - -type GoCallback struct{} - -func (p *GoCallback) Run() string { - return "GoCallback.Run" -} diff --git a/misc/swig/callback/callback.h b/misc/swig/callback/callback.h deleted file mode 100644 index 4b661060d8..0000000000 --- a/misc/swig/callback/callback.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2011 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. - -class Callback { -public: - virtual ~Callback() { } - virtual std::string run() { return "Callback::run"; } -}; - -class Caller { -private: - Callback *callback_; -public: - Caller(): callback_(0) { } - ~Caller() { delCallback(); } - void delCallback() { delete callback_; callback_ = 0; } - void setCallback(Callback *cb) { delCallback(); callback_ = cb; } - std::string call(); -}; diff --git a/misc/swig/callback/callback.swigcxx b/misc/swig/callback/callback.swigcxx deleted file mode 100644 index 6181fe9c7e..0000000000 --- a/misc/swig/callback/callback.swigcxx +++ /dev/null @@ -1,18 +0,0 @@ -/* Copyright 2011 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. */ - -/* An example of writing a C++ virtual function in Go. */ - -%module(directors="1") callback - -%{ -#include -#include "callback.h" -%} - -%include "std_string.i" - -%feature("director"); - -%include "callback.h" diff --git a/misc/swig/callback/callback_test.go b/misc/swig/callback/callback_test.go deleted file mode 100644 index 0c8a3004da..0000000000 --- a/misc/swig/callback/callback_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 callback - -import ( - "testing" -) - -func TestCall(t *testing.T) { - c := NewCaller() - cb := NewCallback() - - c.SetCallback(cb) - s := c.Call() - if s != "Callback::run" { - t.Errorf("unexpected string from Call: %q", s) - } - c.DelCallback() -} - -func TestCallback(t *testing.T) { - c := NewCaller() - cb := NewDirectorCallback(&GoCallback{}) - c.SetCallback(cb) - s := c.Call() - if s != "GoCallback.Run" { - t.Errorf("unexpected string from Call with callback: %q", s) - } - c.DelCallback() - DeleteDirectorCallback(cb) -} diff --git a/misc/swig/nocgo_test.go b/misc/swig/nocgo_test.go new file mode 100644 index 0000000000..c68b97d6b6 --- /dev/null +++ b/misc/swig/nocgo_test.go @@ -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 diff --git a/misc/swig/stdio/file.go b/misc/swig/stdio/file.go deleted file mode 100644 index a582f776f6..0000000000 --- a/misc/swig/stdio/file.go +++ /dev/null @@ -1,15 +0,0 @@ -// 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. -// file.swig turns into a file also named file.go. -// Make sure cmd/go keeps them separate -// when both are passed to cgo. - -package file - -//int F(void) { return 1; } -import "C" - -func F() int { return int(C.F()) } diff --git a/misc/swig/stdio/file.swig b/misc/swig/stdio/file.swig deleted file mode 100644 index b28ae0a6b7..0000000000 --- a/misc/swig/stdio/file.swig +++ /dev/null @@ -1,24 +0,0 @@ -/* Copyright 2011 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. */ - -/* A trivial example of wrapping a C library using SWIG. */ - -%{ -#include -#include -%} - -%typemap(gotype) const char * "string" -%typemap(in) const char * %{ - $1 = malloc($input.n + 1); - memcpy($1, $input.p, $input.n); - $1[$input.n] = '\0'; -%} -%typemap(freearg) const char * %{ - free($1); -%} - -FILE *fopen(const char *name, const char *mode); -int fclose(FILE *); -int fgetc(FILE *); diff --git a/misc/swig/stdio/file_test.go b/misc/swig/stdio/file_test.go deleted file mode 100644 index aea92aafd5..0000000000 --- a/misc/swig/stdio/file_test.go +++ /dev/null @@ -1,28 +0,0 @@ -// 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 file - -import "testing" - -// Open this file itself and verify that the first few characters are -// as expected. -func TestRead(t *testing.T) { - f := Fopen("file_test.go", "r") - if f.Swigcptr() == 0 { - t.Fatal("fopen failed") - } - if Fgetc(f) != '/' || Fgetc(f) != '/' || Fgetc(f) != ' ' || Fgetc(f) != 'C' { - t.Error("read unexpected characters") - } - if Fclose(f) != 0 { - t.Error("fclose failed") - } -} - -func TestF(t *testing.T) { - if x := F(); x != 1 { - t.Fatalf("x = %d, want 1", x) - } -} diff --git a/misc/swig/swig_test.go b/misc/swig/swig_test.go new file mode 100644 index 0000000000..cbe062ae41 --- /dev/null +++ b/misc/swig/swig_test.go @@ -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") + } +} diff --git a/misc/swig/testdata/callback/main.cc b/misc/swig/testdata/callback/main.cc new file mode 100644 index 0000000000..7de917cde4 --- /dev/null +++ b/misc/swig/testdata/callback/main.cc @@ -0,0 +1,15 @@ +// Copyright 2013 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 .cc file will be automatically compiled by the go tool and +// included in the package. + +#include +#include "main.h" + +std::string Caller::call() { + if (callback_ != 0) + return callback_->run(); + return ""; +} diff --git a/misc/swig/testdata/callback/main.go b/misc/swig/testdata/callback/main.go new file mode 100644 index 0000000000..73034a0c7c --- /dev/null +++ b/misc/swig/testdata/callback/main.go @@ -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) +} diff --git a/misc/swig/testdata/callback/main.h b/misc/swig/testdata/callback/main.h new file mode 100644 index 0000000000..4b661060d8 --- /dev/null +++ b/misc/swig/testdata/callback/main.h @@ -0,0 +1,20 @@ +// Copyright 2011 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. + +class Callback { +public: + virtual ~Callback() { } + virtual std::string run() { return "Callback::run"; } +}; + +class Caller { +private: + Callback *callback_; +public: + Caller(): callback_(0) { } + ~Caller() { delCallback(); } + void delCallback() { delete callback_; callback_ = 0; } + void setCallback(Callback *cb) { delCallback(); callback_ = cb; } + std::string call(); +}; diff --git a/misc/swig/testdata/callback/main.swigcxx b/misc/swig/testdata/callback/main.swigcxx new file mode 100644 index 0000000000..0fd73d6362 --- /dev/null +++ b/misc/swig/testdata/callback/main.swigcxx @@ -0,0 +1,18 @@ +/* Copyright 2011 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. */ + +/* An example of writing a C++ virtual function in Go. */ + +%module(directors="1") callback + +%{ +#include +#include "main.h" +%} + +%include "std_string.i" + +%feature("director"); + +%include "main.h" diff --git a/misc/swig/testdata/stdio/main.go b/misc/swig/testdata/stdio/main.go new file mode 100644 index 0000000000..0296dd3224 --- /dev/null +++ b/misc/swig/testdata/stdio/main.go @@ -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) +} diff --git a/misc/swig/testdata/stdio/main.swig b/misc/swig/testdata/stdio/main.swig new file mode 100644 index 0000000000..b28ae0a6b7 --- /dev/null +++ b/misc/swig/testdata/stdio/main.swig @@ -0,0 +1,24 @@ +/* Copyright 2011 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. */ + +/* A trivial example of wrapping a C library using SWIG. */ + +%{ +#include +#include +%} + +%typemap(gotype) const char * "string" +%typemap(in) const char * %{ + $1 = malloc($input.n + 1); + memcpy($1, $input.p, $input.n); + $1[$input.n] = '\0'; +%} +%typemap(freearg) const char * %{ + free($1); +%} + +FILE *fopen(const char *name, const char *mode); +int fclose(FILE *); +int fgetc(FILE *); -- cgit v1.2.3-54-g00ecf