Bind attempts to generate bindings for everything a package exports, generating an error for what it cannot handle. For multiple bound packages, unexporting what should not be bound is sometimes awkward or outright impossible. Lacking the equivalent of Cgo's //export directory, this CL change the behaviour of bind to simply ignore everything it can't generate bindings for, even if otherwise exported. For every declaration it ignores, a comment is generated instead, to help any confusion as to why a particular export was not included. Change-Id: I2c7a5bee0f19a58009293b4e5ac2c95687e62e80 Reviewed-on: https://go-review.googlesource.com/20651 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
280 lines
6.4 KiB
Go
280 lines
6.4 KiB
Go
package bind
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"go/types"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func init() {
|
|
log.SetFlags(log.Lshortfile)
|
|
}
|
|
|
|
var updateFlag = flag.Bool("update", false, "Update the golden files.")
|
|
|
|
var tests = []string{
|
|
"testdata/basictypes.go",
|
|
"testdata/structs.go",
|
|
"testdata/interfaces.go",
|
|
"testdata/issue10788.go",
|
|
"testdata/issue12328.go",
|
|
"testdata/issue12403.go",
|
|
"testdata/try.go",
|
|
"testdata/vars.go",
|
|
"testdata/ignore.go",
|
|
}
|
|
|
|
var fset = token.NewFileSet()
|
|
|
|
func typeCheck(t *testing.T, filename string) *types.Package {
|
|
f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
|
|
if err != nil {
|
|
t.Fatalf("%s: %v", filename, err)
|
|
}
|
|
|
|
pkgName := filepath.Base(filename)
|
|
pkgName = strings.TrimSuffix(pkgName, ".go")
|
|
|
|
// typecheck and collect typechecker errors
|
|
var conf types.Config
|
|
conf.Error = func(err error) {
|
|
t.Error(err)
|
|
}
|
|
pkg, err := conf.Check(pkgName, fset, []*ast.File{f}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return pkg
|
|
}
|
|
|
|
// diff runs the command "diff a b" and returns its output
|
|
func diff(a, b string) string {
|
|
var buf bytes.Buffer
|
|
var cmd *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "plan9":
|
|
cmd = exec.Command("/bin/diff", "-c", a, b)
|
|
default:
|
|
cmd = exec.Command("/usr/bin/diff", "-u", a, b)
|
|
}
|
|
cmd.Stdout = &buf
|
|
cmd.Stderr = &buf
|
|
cmd.Run()
|
|
return buf.String()
|
|
}
|
|
|
|
func writeTempFile(t *testing.T, name string, contents []byte) string {
|
|
f, err := ioutil.TempFile("", name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := f.Write(contents); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return f.Name()
|
|
}
|
|
|
|
func TestGenObjc(t *testing.T) {
|
|
var suffixes = map[fileType]string{
|
|
ObjcH: ".objc.h.golden",
|
|
ObjcM: ".objc.m.golden",
|
|
ObjcGoH: ".objc.go.h.golden",
|
|
}
|
|
|
|
for _, filename := range tests {
|
|
pkg := typeCheck(t, filename)
|
|
|
|
for typ, suffix := range suffixes {
|
|
var buf bytes.Buffer
|
|
conf := &GeneratorConfig{
|
|
Writer: &buf,
|
|
Fset: fset,
|
|
Pkg: pkg,
|
|
AllPkg: []*types.Package{pkg},
|
|
}
|
|
if err := GenObjc(conf, "", typ); err != nil {
|
|
t.Errorf("%s: %v", filename, err)
|
|
continue
|
|
}
|
|
out := writeTempFile(t, "generated"+suffix, buf.Bytes())
|
|
defer os.Remove(out)
|
|
golden := filename[:len(filename)-len(".go")] + suffix
|
|
if diffstr := diff(golden, out); diffstr != "" {
|
|
t.Errorf("%s: does not match Objective-C golden:\n%s", filename, diffstr)
|
|
if *updateFlag {
|
|
t.Logf("Updating %s...", golden)
|
|
err := exec.Command("/bin/cp", out, golden).Run()
|
|
if err != nil {
|
|
t.Errorf("Update failed: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenJava(t *testing.T) {
|
|
var suffixes = map[fileType]string{
|
|
Java: ".java.golden",
|
|
JavaC: ".java.c.golden",
|
|
JavaH: ".java.h.golden",
|
|
}
|
|
|
|
for _, filename := range tests {
|
|
pkg := typeCheck(t, filename)
|
|
for typ, suffix := range suffixes {
|
|
var buf bytes.Buffer
|
|
conf := &GeneratorConfig{
|
|
Writer: &buf,
|
|
Fset: fset,
|
|
Pkg: pkg,
|
|
AllPkg: []*types.Package{pkg},
|
|
}
|
|
if err := GenJava(conf, "", typ); err != nil {
|
|
t.Errorf("%s: %v", filename, err)
|
|
continue
|
|
}
|
|
out := writeTempFile(t, "generated"+suffix, buf.Bytes())
|
|
defer os.Remove(out)
|
|
golden := filename[:len(filename)-len(".go")] + suffix
|
|
if diffstr := diff(golden, out); diffstr != "" {
|
|
t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)
|
|
|
|
if *updateFlag {
|
|
t.Logf("Updating %s...", golden)
|
|
if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
|
|
t.Errorf("Update failed: %s", err)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenGo(t *testing.T) {
|
|
for _, filename := range tests {
|
|
var buf bytes.Buffer
|
|
pkg := typeCheck(t, filename)
|
|
conf := &GeneratorConfig{
|
|
Writer: &buf,
|
|
Fset: fset,
|
|
Pkg: pkg,
|
|
AllPkg: []*types.Package{pkg},
|
|
}
|
|
if err := GenGo(conf); err != nil {
|
|
t.Errorf("%s: %v", filename, err)
|
|
continue
|
|
}
|
|
out := writeTempFile(t, "go", buf.Bytes())
|
|
defer os.Remove(out)
|
|
golden := filename + ".golden"
|
|
if diffstr := diff(golden, out); diffstr != "" {
|
|
t.Errorf("%s: does not match Java golden:\n%s", filename, diffstr)
|
|
|
|
if *updateFlag {
|
|
t.Logf("Updating %s...", golden)
|
|
if err := exec.Command("/bin/cp", out, golden).Run(); err != nil {
|
|
t.Errorf("Update failed: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCustomPrefix(t *testing.T) {
|
|
const datafile = "testdata/customprefix.go"
|
|
const isHeader = true
|
|
pkg := typeCheck(t, datafile)
|
|
|
|
conf := &GeneratorConfig{
|
|
Fset: fset,
|
|
Pkg: pkg,
|
|
AllPkg: []*types.Package{pkg},
|
|
}
|
|
testCases := []struct {
|
|
golden string
|
|
gen func(w io.Writer) error
|
|
}{
|
|
{
|
|
"testdata/customprefix.java.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", Java) },
|
|
},
|
|
{
|
|
"testdata/customprefix.java.h.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", JavaH) },
|
|
},
|
|
{
|
|
"testdata/customprefix.java.c.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenJava(conf, "com.example", JavaC) },
|
|
},
|
|
{
|
|
"testdata/customprefix.objc.go.h.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcGoH) },
|
|
},
|
|
{
|
|
"testdata/customprefix.objc.h.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcH) },
|
|
},
|
|
{
|
|
"testdata/customprefix.objc.m.golden",
|
|
func(w io.Writer) error { conf.Writer = w; return GenObjc(conf, "EX", ObjcM) },
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
var buf bytes.Buffer
|
|
if err := tc.gen(&buf); err != nil {
|
|
t.Errorf("generating %s: %v", tc.golden, err)
|
|
continue
|
|
}
|
|
out := writeTempFile(t, "generated", buf.Bytes())
|
|
defer os.Remove(out)
|
|
if diffstr := diff(tc.golden, out); diffstr != "" {
|
|
t.Errorf("%s: generated file does not match:\b%s", tc.golden, diffstr)
|
|
if *updateFlag {
|
|
t.Logf("Updating %s...", tc.golden)
|
|
err := exec.Command("/bin/cp", out, tc.golden).Run()
|
|
if err != nil {
|
|
t.Errorf("Update failed: %s", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLowerFirst(t *testing.T) {
|
|
testCases := []struct {
|
|
in, want string
|
|
}{
|
|
{"", ""},
|
|
{"Hello", "hello"},
|
|
{"HelloGopher", "helloGopher"},
|
|
{"hello", "hello"},
|
|
{"ID", "id"},
|
|
{"IDOrName", "idOrName"},
|
|
{"ΓειαΣας", "γειαΣας"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
if got := lowerFirst(tc.in); got != tc.want {
|
|
t.Errorf("lowerFirst(%q) = %q; want %q", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|