gopy/main_test.go

1093 lines
27 KiB
Go

// Copyright 2015 The go-python 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 (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"github.com/go-python/gopy/bind"
)
var (
testBackends = map[string]string{}
features = map[string][]string{
"_examples/hi": []string{"py3"},
"_examples/gobytes": []string{"py3"},
"_examples/funcs": []string{"py3"},
"_examples/sliceptr": []string{"py3"},
"_examples/simple": []string{"py3"},
"_examples/empty": []string{"py3"},
"_examples/named": []string{"py3"},
"_examples/structs": []string{"py3"},
"_examples/consts": []string{"py3"},
"_examples/vars": []string{"py3"},
"_examples/seqs": []string{"py3"},
"_examples/cgo": []string{"py3"},
"_examples/pyerrors": []string{"py3"},
"_examples/iface": []string{"py3"},
"_examples/pointers": []string{"py3"},
"_examples/arrays": []string{"py3"},
"_examples/slices": []string{"py3"},
"_examples/maps": []string{"py3"},
"_examples/gostrings": []string{"py3"},
"_examples/rename": []string{"py3"},
"_examples/lot": []string{"py3"},
"_examples/unicode": []string{"py3"},
"_examples/osfile": []string{"py3"},
"_examples/gopygc": []string{"py3"},
"_examples/cstrings": []string{"py3"},
"_examples/pkgconflict": []string{"py3"},
"_examples/variadic": []string{"py3"},
}
testEnvironment = os.Environ()
)
func init() {
os.Setenv("GOFLAGS", "-mod=mod")
testEnvironment = append(testEnvironment, "GOFLAGS=-mod=mod")
}
func TestGovet(t *testing.T) {
cmd := exec.Command("go", "vet", "./...")
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
err := cmd.Run()
if err != nil {
t.Fatalf("error running %s:\n%s\n%v", "go vet", buf.String(), err)
}
}
func TestGofmt(t *testing.T) {
exe, err := exec.LookPath("goimports")
if err != nil {
switch e := err.(type) {
case *exec.Error:
if e.Err == exec.ErrNotFound {
exe, err = exec.LookPath("gofmt")
}
}
}
if err != nil {
t.Fatal(err)
}
cmd := exec.Command(exe, "-d", ".")
buf := new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
err = cmd.Run()
if err != nil {
t.Fatalf("error running %s:\n%s\n%v", exe, buf.String(), err)
}
if len(buf.Bytes()) != 0 {
t.Errorf("some files were not gofmt'ed:\n%s\n", buf.String())
}
}
func TestGoPyErrors(t *testing.T) {
pyvm := testBackends["py3"]
workdir, err := os.MkdirTemp("", "gopy-")
if err != nil {
t.Fatalf("could not create workdir: %v\n", err)
}
t.Logf("pyvm: %s making work dir: %s\n", pyvm, workdir)
defer os.RemoveAll(workdir)
curPkgPath := reflect.TypeOf(pkg{}).PkgPath()
fpath := filepath.Join(curPkgPath, "_examples/gopyerrors")
cmd := exec.Command("go", "run", ".", "gen", "-vm="+pyvm, "-output="+workdir, fpath)
t.Logf("running: %v\n", cmd.Args)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("could not run %v: %+v\n", strings.Join(cmd.Args, " "), err)
}
contains := `--- Processing package: github.com/go-python/gopy/_examples/gopyerrors ---
ignoring python incompatible function: .func github.com/go-python/gopy/_examples/gopyerrors.NotErrorMany() (int, int): func() (int, int): gopy: second result value must be of type error: func() (int, int)
ignoring python incompatible method: gopyerrors.func (*github.com/go-python/gopy/_examples/gopyerrors.Struct).NotErrorMany() (int, string): func() (int, string): gopy: second result value must be of type error: func() (int, string)
ignoring python incompatible method: gopyerrors.func (*github.com/go-python/gopy/_examples/gopyerrors.Struct).TooMany() (int, int, string): func() (int, int, string): gopy: too many results to return: func() (int, int, string)
ignoring python incompatible function: .func github.com/go-python/gopy/_examples/gopyerrors.TooMany() (int, int, string): func() (int, int, string): gopy: too many results to return: func() (int, int, string)
`
if got, want := string(out), contains; !strings.Contains(got, want) {
t.Fatalf("%v does not contain\n%v\n", got, want)
}
}
func TestHi(t *testing.T) {
// t.Parallel()
path := "_examples/hi"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`hi from go
hello you from go
working...
worked for 2 hours
working...
working...
worked for 4 hours
--- doc(hi)...
package hi exposes a few Go functions to be wrapped and used from Python.
--- hi.Universe: 42
--- hi.Version: 0.1
--- hi.Debug(): False
--- hi.Set_Debug(true)
--- hi.Debug(): True
--- hi.Set_Debug(false)
--- hi.Debug(): False
--- hi.Anon(): hi.Person{Name="<nobody>", Age=1}
--- new anon: hi.Person{Name="you", Age=24}
--- hi.Set_Anon(hi.NewPerson('you', 24))...
--- hi.Anon(): hi.Person{Name="you", Age=24}
--- doc(hi.Hi)...
Hi()
Hi prints hi from Go
--- hi.Hi()...
--- doc(hi.Hello)...
Hello(str s)
Hello prints a greeting from Go
--- hi.Hello('you')...
--- doc(hi.Add)...
Add(int i, int j) int
Add returns the sum of its arguments.
--- hi.Add(1, 41)...
42
--- hi.Concat('4', '2')...
42
--- hi.LookupQuestion(42)...
Life, the Universe and Everything
--- hi.LookupQuestion(12)...
caught: Wrong answer: 12 != 42
--- doc(hi.Person):
Person is a simple struct
--- p = hi.Person()...
--- p: hi.Person{Name="", Age=0}
--- p.Name:
--- p.Age: 0
--- doc(hi.Greet):
Greet() str
Greet sends greetings
--- p.Greet()...
Hello, I am
--- p.String()...
hi.Person{Name="", Age=0}
--- doc(p):
Person is a simple struct
--- p.Name = "foo"...
--- p.Age = 42...
--- p.String()...
hi.Person{Name="foo", Age=42}
--- p.Age: 42
--- p.Name: foo
--- p.Work(2)...
--- p.Work(24)...
caught: can't work for 24 hours!
--- p.Salary(2): 20
--- p.Salary(24): caught: can't work for 24 hours!
--- Person.__init__
caught: argument 2 must be str, not int | err-type: <class 'TypeError'>
caught: 'str' object cannot be interpreted as an integer | err-type: <class 'TypeError'>
*ERROR* no exception raised!
hi.Person{Name="name", Age=0}
hi.Person{Name="name", Age=42}
hi.Person{Name="name", Age=42}
hi.Person{Name="name", Age=42}
--- hi.NewPerson('me', 666): hi.Person{Name="me", Age=666}
--- hi.NewPersonWithAge(666): hi.Person{Name="stranger", Age=666}
--- hi.NewActivePerson(4):
hi.Person{Name="", Age=0}
--- c = hi.Couple()...
hi.Couple{P1=hi.Person{Name="", Age=0}, P2=hi.Person{Name="", Age=0}}
--- c.P1: hi.Person{Name="", Age=0}
--- c: hi.Couple{P1=hi.Person{Name="tom", Age=5}, P2=hi.Person{Name="bob", Age=2}}
--- c = hi.NewCouple(tom, bob)...
hi.Couple{P1=hi.Person{Name="tom", Age=50}, P2=hi.Person{Name="bob", Age=41}}
hi.Couple{P1=hi.Person{Name="mom", Age=50}, P2=hi.Person{Name="bob", Age=51}}
--- Couple.__init__
hi.Couple{P1=hi.Person{Name="p1", Age=42}, P2=hi.Person{Name="p2", Age=52}}
hi.Couple{P1=hi.Person{Name="p1", Age=42}, P2=hi.Person{Name="p2", Age=52}}
hi.Couple{P1=hi.Person{Name="p2", Age=52}, P2=hi.Person{Name="p1", Age=42}}
caught: supplied argument type <class 'int'> is not a go.GoClass | err-type: <class 'TypeError'>
caught: supplied argument type <class 'int'> is not a go.GoClass | err-type: <class 'TypeError'>
caught: supplied argument type <class 'int'> is not a go.GoClass | err-type: <class 'TypeError'>
--- testing GC...
--- len(objs): 100000
--- len(vs): 100000
--- testing GC... [ok]
--- testing array...
arr: hi.Array_2_int len: 2 handle: 300036 [1, 2]
len(arr): 2
arr[0]: 1
arr[1]: 2
arr[2]: caught: slice index out of range
arr: hi.Array_2_int len: 2 handle: 300036 [1, 2]
len(arr): 2
mem(arr): caught: memoryview: a bytes-like object is required, not 'Array_2_int'
--- testing slice...
slice: go.Slice_int len: 2 handle: 300037 [1, 2]
len(slice): 2
slice[0]: 1
slice[1]: 2
slice[2]: caught: slice index out of range
slice: go.Slice_int len: 2 handle: 300037 [1, 42]
slice repr: go.Slice_int([1, 42])
len(slice): 2
mem(slice): caught: memoryview: a bytes-like object is required, not 'Slice_int'
OK
`),
})
}
func TestBytes(t *testing.T) {
// t.Parallel()
path := "_examples/gobytes"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`Python bytes: b'\x00\x01\x02\x03'
Go slice: go.Slice_byte len: 10 handle: 1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
gobytes.HashBytes from Go bytes: gobytes.Array_4_byte len: 4 handle: 2 [12, 13, 81, 81]
Python bytes to Go: go.Slice_byte len: 4 handle: 3 [0, 1, 2, 3]
Go bytes to Python: b'\x03\x04\x05'
OK
`),
})
}
func TestBindFuncs(t *testing.T) {
// t.Parallel()
path := "_examples/funcs"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`got return value: true
got nil
ofs FieldI: 42 FieldS: str field
fs.CallBack(22, cbfun)...
in python cbfun: FieldI: 42 FieldS: str field ival: 22 sval: str field
fs.CallBackIf(22, cbfunif)...
in python cbfunif: FieldI: 42 FieldS: str field ival: 22 ifval: str field
fs.CallBackRval(22, cbfunrval)...
in python cbfunrval: FieldI: 42 FieldS: str field ival: 22 ifval: str field
fs.CallBack(32, cls.ClassFun)...
in python class fun: FieldI: 42 FieldS: str field ival: 32 sval: str field
cls.CallSelf...
in python class fun: FieldI: 42 FieldS: str field ival: 77 sval: str field
fs.ObjArg with nil
fs.ObjArg with fs
OK
`),
})
}
func TestBindSimple(t *testing.T) {
// t.Parallel()
path := "_examples/simple"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`doc(pkg):
'\nsimple is a simple package.\n\n'
pkg.Func()...
fct = pkg.Func...
fct()...
pkg.Add(1,2)= 3
pkg.Bool(True)= True
pkg.Bool(False)= False
pkg.Comp64Add((3+4j), (2+5j)) = (5+9j)
pkg.Comp128Add((3+4j), (2+5j)) = (5+9j)
OK
`),
})
}
func TestBindEmpty(t *testing.T) {
// t.Parallel()
path := "_examples/empty"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`doc(pkg):
'\nPackage empty does not expose anything.\nWe may want to wrap and import it just for its side-effects.\n\n'
OK
`),
})
}
func TestBindPointers(t *testing.T) {
// t.Parallel()
path := "_examples/pointers"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`s = pointers.S(2)
s = pointers.S{Value=2, handle=1}
s.Value = 2
OK
`),
})
}
func TestBindNamed(t *testing.T) {
// t.Parallel()
path := "_examples/named"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`doc(named): '\npackage named tests various aspects of named types.\n\n'
s = named.Slice()
s = named.named_Slice len: 0 handle: 1 []
s = named.Slice([1,2])
s = named.named_Slice len: 2 handle: 2 [1.0, 2.0]
s = named.Slice(range(10))
s = named.named_Slice len: 10 handle: 3 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
s = named.Slice(xrange(10))
s = named.named_Slice len: 10 handle: 4 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
OK
`),
})
}
func TestBindStructs(t *testing.T) {
// t.Parallel()
path := "_examples/structs"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`s = structs.S()
s = structs.S{handle=1}
s.Init()
s.Upper('boo')= 'BOO'
s1 = structs.S1()
s1 = structs.S1{handle=2}
caught error: 'S1' object has no attribute 'private'
s2 = structs.S2()
s2 = structs.S2{Public=1, handle=5}
s2 = structs.S2{Public=42, handle=7}
s2.Public = 42
caught error: 'S2' object has no attribute 'private'
s2child = S2Child{S2: structs.S2{Public=42, handle=8, local=123}, local: 123}
s2child.Public = 42
s2child.local = 123
caught error: 'S2Child' object has no attribute 'private'
s3.X,Y = 3,4
OK
`),
})
}
func TestBindConsts(t *testing.T) {
// t.Parallel()
path := "_examples/consts"
// NOTE: python2 does not properly output .666 decimals for unknown reasons.
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`c1 = c1
c2 = 42
c3 = 666.666
c4 = c4
c5 = 42
c6 = 42
c7 = 666.666
k1 = 1
k2 = 2
OK
`),
})
}
func TestBindVars(t *testing.T) {
// t.Parallel()
path := "_examples/vars"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`doc(vars):
None
doc(vars.V1()):
'\n\tV1 Gets Go Variable: vars.V1\n\t\n\t'
doc(vars.Set_V1()):
'\n\tSet_V1 Sets Go Variable: vars.V1\n\t\n\t'
Initial values
v1 = v1
v2 = 42
v3 = 666.666
v4 = c4
v5 = 42
v6 = 42
v7 = 666.666
k1 = 1
k2 = 2
New values
v1 = test1
v2 = 90
v3 = 1111.1111
v4 = test2
v5 = 50
v6 = 50
v7 = 1111.1111
k1 = 123
k2 = 456
vars.Doc() = 'A variable with some documentation'
doc of vars.Doc = '\n\tDoc Gets Go Variable: vars.Doc\n\tDoc is a top-level string with some documentation attached.\n\t\n\t'
doc of vars.Set_Doc = '\n\tSet_Doc Sets Go Variable: vars.Doc\n\tDoc is a top-level string with some documentation attached.\n\t\n\t'
OK
`),
})
}
func TestBindSeqs(t *testing.T) {
// t.Parallel()
path := "_examples/seqs"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`doc(seqs): '\npackage seqs tests various aspects of sequence types.\n\n'
s = seqs.Slice()
s = seqs.seqs_Slice len: 0 handle: 1 []
s = seqs.Slice([1,2])
s = seqs.seqs_Slice len: 2 handle: 2 [1.0, 2.0]
s = seqs.Slice(range(10))
s = seqs.seqs_Slice len: 10 handle: 3 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
s = seqs.Slice(xrange(10))
s = seqs.seqs_Slice len: 10 handle: 4 [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]
s = seqs.Slice()
s = seqs.seqs_Slice len: 0 handle: 5 []
s += [1,2]
s = seqs.seqs_Slice len: 2 handle: 5 [1.0, 2.0]
s += [10,20]
s = seqs.seqs_Slice len: 4 handle: 5 [1.0, 2.0, 10.0, 20.0]
OK
`),
})
}
func TestBindInterfaces(t *testing.T) {
// t.Parallel()
path := "_examples/iface"
// NOTE: python2 outputs this in a different order, so test fails, but results are the same
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`t.F [CALLED]
iface.CallIface...
t.F [CALLED]
iface.CallIface... [DONE]
iface as string: test string
iface as string: 42
iface as handle: &{0 }
iface as handle: <nil>
doc(iface): '\npackage iface tests various aspects of interfaces.\n\n'
t = iface.T()
t.F()
iface.CallIface(t)
iface.IfaceString("test string"
iface.IfaceString(str(42))
iface.IfaceHandle(t)
iface.IfaceHandle(go.nil)
OK
`),
})
}
func TestBindCgoPackage(t *testing.T) {
// t.Parallel()
path := "_examples/cgo"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`cgo.doc: '\nPackage cgo tests bindings of CGo-based packages.\n\n'
cgo.Hi()= 'hi from go\n'
cgo.Hello(you)= 'hello you from go\n'
OK
`),
})
}
func TestPyErrors(t *testing.T) {
// t.Parallel()
path := "_examples/pyerrors"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`Divide by zero.
pyerrors.Div(5, 2) = 2
Empty string value.
pyerrors.NewMyString("hello") = "hello"
OK
`),
})
}
func TestBuiltinArrays(t *testing.T) {
// t.Parallel()
path := "_examples/arrays"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`Python list: [1, 2, 3, 4]
Go array: arrays.Array_4_int len: 4 handle: 1 [1, 2, 3, 4]
arrays.IntSum from Go array: 10
OK
`),
})
}
func TestBuiltinSlices(t *testing.T) {
// t.Parallel()
path := "_examples/slices"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`0: S0
1: S1
2: S2
S0
S1
Python list: [1, 2, 3, 4]
Go slice: go.Slice_int len: 4 handle: 1 [1, 2, 3, 4]
slices.IntSum from Python list: 10
slices.IntSum from Go slice: 10
unsigned slice elements: 1 2 3 4
signed slice elements: -1 -2 -3 -4
struct slice: slices.Slice_Ptr_slices_S len: 3 handle: 11 [slices.S{Name=S0, handle=12}, slices.S{Name=S1, handle=13}, slices.S{Name=S2, handle=14}]
struct slice[0]: slices.S{Name=S0, handle=15}
struct slice[1]: slices.S{Name=S1, handle=16}
struct slice[2].Name: S2
[][]bool working as expected
OK
`),
})
}
func TestBuiltinMaps(t *testing.T) {
// t.Parallel()
path := "_examples/maps"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`map a[1]: 3.0
map a[2]: 5.0
2 in map: True
3 in map: False
maps.Sum from Go map: 8.0
map b: {1: 3.0, 2: 5.0}
maps.Sum from Python dictionary: 8.0
maps.Keys from Go map: go.Slice_int len: 2 handle: 3 [1, 2]
maps.Values from Go map: go.Slice_float64 len: 2 handle: 4 [3.0, 5.0]
maps.Keys from Python dictionary: go.Slice_int len: 2 handle: 6 [1, 2]
maps.Values from Python dictionary: go.Slice_float64 len: 2 handle: 8 [3.0, 5.0]
deleted 1 from a: maps.Map_int_float64 len: 1 handle: 1 {2=5.0, }
OK
`),
})
}
func TestBindStrings(t *testing.T) {
// t.Parallel()
path := "_examples/gostrings"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`S1 = S1
GetString() = MyString
OK
`),
})
}
func TestBindRename(t *testing.T) {
// t.Parallel()
path := "_examples/rename"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: []string{"-rename"},
want: []byte(`say_hi_fn(): hi
MyStruct().say_something(): something
MyStruct.auto_renamed_property.__doc__: I should be renamed to auto_renamed_property
when generated with -rename flag
MyStruct.custom_name.__doc__: I should be renamed to custom_name with the custom option
OK
`),
})
}
func TestLot(t *testing.T) {
// t.Parallel()
path := "_examples/lot"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`l.SomeString : some string
l.SomeInt : 1337
l.SomeFloat : 1337.1337
l.SomeBool : True
l.SomeListOfStrings: go.Slice_string len: 4 handle: 2 [some, list, of, strings]
l.SomeListOfInts: go.Slice_int64 len: 4 handle: 3 [6, 2, 9, 1]
l.SomeListOfFloats: go.Slice_float64 len: 4 handle: 4 [6.6, 2.2, 9.9, 1.1]
l.SomeListOfBools: go.Slice_bool len: 4 handle: 5 [True, False, True, False]
OK
`),
})
}
func TestSlicePtr(t *testing.T) {
// t.Parallel()
path := "_examples/sliceptr"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`sliceptr.sliceptr_IntVector len: 3 handle: 1 [1, 2, 3]
sliceptr.sliceptr_IntVector len: 4 handle: 1 [1, 2, 3, 4]
sliceptr.sliceptr_StrVector len: 4 handle: 2 [1, 2, 3, 4]
OK
`),
})
}
func TestUnicode(t *testing.T) {
// t.Parallel()
path := "_examples/unicode"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`encoding.HandleString(unicodestr) -> Python Unicode string 🐱
encoding.GetString() -> Go Unicode string 🐱
OK
`),
})
}
func TestPYGC(t *testing.T) {
// t.Parallel()
path := "_examples/gopygc"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`0
3
0
3
5
6
7
8
5
3
2
1
0
1
0
1
1
1
0
OK
`),
})
}
func TestCStrings(t *testing.T) {
// t.Parallel()
path := "_examples/cstrings"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
// todo: this test on mac leaks everything except String
want: []byte(`gofnString leaked: False
gofnStruct leaked: False
gofnNestedStruct leaked: False
gofnSlice leaked: False
gofnMap leaked: False
OK
`),
})
}
func TestPackagePrefix(t *testing.T) {
// t.Parallel()
path := "_examples/package/mypkg"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
outputdir: "mypkg",
pkgprefix: ".",
testdir: ".",
want: []byte(`mypkg.mypkg.SayHello()...
Hello
OK
`),
})
}
func TestPkgConflict(t *testing.T) {
// t.Parallel()
path := "_examples/pkgconflict"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`17 items are made of wool
42 items are made of hyperlinks
2 items are made of fuzz
`),
})
}
// see notes in _examples/osfile/test.py for why this doesn't work..
// leaving here for now in case someone wants to follow-up and make it work..
//
// func TestOsFile(t *testing.T) {
// // t.Parallel()
// path := "_examples/osfile"
// testPkg(t, pkg{
// path: path,
// lang: features[path],
// cmd: "pkg",
// extras: []string{"os"},
// want: []byte(`yeah..
// OK
// `),
// })
// }
func TestBindVariadic(t *testing.T) {
// t.Parallel()
path := "_examples/variadic"
testPkg(t, pkg{
path: path,
lang: features[path],
cmd: "build",
extras: nil,
want: []byte(`NonVariadic 1+[2+3+4]+5 = 15
Variadic 1+2+3+4+5 = 15
Variadic Struct s(1)+s(2)+s(3) = 6
Variadic InterFace i(1)+i(2)+i(3) = 6
Type OK
`),
})
}
// Generate / verify SUPPORT_MATRIX.md from features map.
func TestCheckSupportMatrix(t *testing.T) {
var buf bytes.Buffer
buf.WriteString("# Support matrix\n")
buf.WriteString(`
NOTE: File auto-generated by TestCheckSupportMatrix in main_test.go. Please
don't modify manually.
`)
// Generate
// - sorted list of features
// - sorted list of backends
// - a map of feature to available backends
var featuresSorted []string
var allBackendsSorted []string
featureToBackendMap := make(map[string]map[string]bool)
allBackends := make(map[string]bool)
for feature, backends := range features {
featuresSorted = append(featuresSorted, feature)
featureToBackendMap[feature] = make(map[string]bool)
for _, backend := range backends {
featureToBackendMap[feature][backend] = true
allBackends[backend] = true
}
}
for backend, _ := range allBackends {
allBackendsSorted = append(allBackendsSorted, backend)
}
sort.Strings(featuresSorted)
sort.Strings(allBackendsSorted)
// Write the table header and the line separating header and rows.
fmt.Fprintf(&buf, "Feature |%s\n", strings.Join(allBackendsSorted, " | "))
var tableDelimiters []string
for i := 0; i <= len(allBackendsSorted); i++ {
tableDelimiters = append(tableDelimiters, "---")
}
buf.WriteString(strings.Join(tableDelimiters, " | "))
buf.WriteString("\n")
// Write the actual rows of the support matrix.
for _, feature := range featuresSorted {
var cells []string
cells = append(cells, feature)
for _, backend := range allBackendsSorted {
if featureToBackendMap[feature][backend] {
cells = append(cells, "yes")
} else {
cells = append(cells, "no")
}
}
buf.WriteString(strings.Join(cells, " | "))
buf.WriteString("\n")
}
if os.Getenv("GOPY_GENERATE_SUPPORT_MATRIX") == "1" {
err := os.WriteFile("SUPPORT_MATRIX.md", buf.Bytes(), 0644)
if err != nil {
log.Fatalf("Unable to write SUPPORT_MATRIX.md")
}
return
}
src, err := os.ReadFile("SUPPORT_MATRIX.md")
if err != nil {
log.Fatalf("Unable to read SUPPORT_MATRIX.md")
}
msg := `
This is a test case to verify the support matrix. This test is likely failing
because the map features has been updated and the
auto-generated file SUPPORT_MATRIX.md hasn't been updated. Please run 'go test'
with environment variable GOPY_GENERATE_SUPPORT_MATRIX=1 to regenerate
SUPPORT_MATRIX.md and commit the changes to SUPPORT_MATRIX.md onto git.
`
if bytes.Compare(buf.Bytes(), src) != 0 {
t.Fatalf(msg)
}
}
type pkg struct {
path string
lang []string
cmd string
outputdir string
pkgprefix string
testdir string
extras []string
want []byte
}
func testPkg(t *testing.T, table pkg) {
backends := []string{"py3"}
// backends := table.lang // todo: enabling py2 testing requires separate "want" output
for _, be := range backends {
fmt.Printf("looping over backends: %s in %s\n", be, backends)
vm, ok := testBackends[be]
if !ok || vm == "" {
// backend not available.
t.Logf("Skipped testing backend %s for %s\n", be, table.path)
continue
}
switch be {
case "py3":
t.Run(be, func(t *testing.T) {
// t.Parallel()
testPkgBackend(t, vm, table)
})
default:
t.Errorf("invalid backend name %q", be)
}
}
}
func writeGoMod(t *testing.T, pkgDir, tstDir string) {
template := `
module dummy
require github.com/go-python/gopy v0.0.0
replace github.com/go-python/gopy => %s
`
contents := fmt.Sprintf(template, pkgDir)
if err := os.WriteFile(filepath.Join(tstDir, "go.mod"), []byte(contents), 0666); err != nil {
t.Fatalf("failed to write go.mod file: %v", err)
}
}
func testPkgBackend(t *testing.T, pyvm string, table pkg) {
curPkgPath := reflect.TypeOf(table).PkgPath()
_, pkgNm := filepath.Split(table.path)
cwd, _ := os.Getwd()
workdir, err := os.MkdirTemp("", "gopy-")
if err != nil {
t.Fatalf("[%s:%s]: could not create workdir: %v\n", pyvm, table.path, err)
}
genPkgDir := workdir
if table.outputdir != "" {
genPkgDir = filepath.Join(workdir, table.outputdir)
fmt.Printf("pyvm: %s making temp output dir: %s\n", pyvm, genPkgDir)
err = os.MkdirAll(genPkgDir, 0700)
if err != nil {
t.Fatalf("[%s:%s]: could not create temp output dir: %v\n", pyvm, table.path, err)
}
}
defer os.RemoveAll(workdir)
defer bind.ResetPackages()
env := make([]string, len(testEnvironment))
copy(env, testEnvironment)
env = append(env, fmt.Sprintf("PYTHONPATH=%s", workdir))
writeGoMod(t, cwd, genPkgDir)
// fmt.Printf("building in work dir: %s\n", workdir)
fpath := "./" + table.path
if table.cmd != "build" { // non-build cases end up inside the working dir -- need a global import path
fpath = filepath.Join(curPkgPath, table.path)
}
args := []string{table.cmd, "-vm=" + pyvm, "-output=" + genPkgDir, "-package-prefix", table.pkgprefix}
if table.extras != nil {
args = append(args, table.extras...)
}
args = append(args, fpath)
fmt.Printf("run cmd: %s\n", args)
err = run(args)
if err != nil {
t.Fatalf("[%s:%s]: error running gopy-build: %v\n", pyvm, table.path, err)
}
// fmt.Printf("copying test.py\n")
tstDir := genPkgDir
if table.cmd != "build" {
tstDir = filepath.Join(workdir, pkgNm)
}
if table.testdir != "" {
tstDir = filepath.Join(workdir, table.testdir)
}
tstSrc := filepath.Join(filepath.Join(cwd, table.path), "test.py")
tstDst := filepath.Join(tstDir, "test.py")
err = copyCmd(tstSrc, tstDst)
if err != nil {
t.Fatalf("[%s:%s]: error copying 'test.py' from: %s to: %s: %v\n", pyvm, table.path, tstSrc, tstDst, err)
}
fmt.Printf("running %s test.py\n", pyvm)
cmd := exec.Command(pyvm, "./test.py")
cmd.Env = env
cmd.Dir = tstDir
cmd.Stdin = os.Stdin
buf, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf(
"[%s:%s]: error running python module: err=%v\n%v\n",
pyvm, table.path,
err,
string(buf),
)
}
var (
got = strings.Replace(string(buf), "\r\n", "\n", -1)
want = strings.Replace(string(table.want), "\r\n", "\n", -1)
)
if !reflect.DeepEqual(got, want) {
diffTxt := ""
diffBin, diffErr := exec.LookPath("diff")
if diffErr == nil {
wantFile, wantErr := os.Create(filepath.Join(workdir, "want.txt"))
if wantErr == nil {
wantFile.Write([]byte(want))
wantFile.Close()
}
gotFile, gotErr := os.Create(filepath.Join(workdir, "got.txt"))
if gotErr == nil {
gotFile.Write([]byte(got))
gotFile.Close()
}
if gotErr == nil && wantErr == nil {
cmd = exec.Command(diffBin, "-urN",
wantFile.Name(),
gotFile.Name(),
)
diff, _ := cmd.CombinedOutput()
diffTxt = string(diff) + "\n"
}
t.Fatalf("[%s:%s]: error running python module:\n%s",
pyvm, table.path,
diffTxt,
)
}
t.Fatalf("[%s:%s]: error running python module:\ngot:\n%s\n\nwant:\n%s\n[%s:%s] diff:\n%s",
pyvm, table.path,
got, want,
pyvm, table.path,
diffTxt,
)
}
}