Skip to content

Commit b463cdc

Browse files
committed
Merge pull request #18 from tanakad/feature/improve-error
Return stack trace error
2 parents 72ecd40 + 3b1deee commit b463cdc

7 files changed

+117
-32
lines changed

p/_test_pyfunc.py

+4
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ class negator(object):
55
def __call__(self, x):
66
return -x
77

8+
89
def divideByZero(n):
910
return n / 0
1011

12+
1113
class alwaysFail(object):
1214
def __call__(self):
1315
return divideByZero(42)
16+
17+
not_func_attr = 'test'

p/_test_syntax_error.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# this python file is not valid, use for only unit test called from goconvey.
2+
3+
4+
class SyntaxErrorTest(object):
5+
6+
def __init__(self):
7+
pass
8+
9+
def test(self, hoge):
10+
print self.hoge = hoge

p/pyerror.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func getPyErr() error {
128128
mainMsg := strings.TrimSpace(extractLineFromFormattedErrorMessage(formatted, ln-1))
129129
syntaxErr := ""
130130
nTracebackLines := ln - 1
131-
if isSyntaxError(C.PyTuple_GetItem(excInfo.p, 0)) {
131+
if isSyntaxError(C.PyTuple_GetItem(excInfo.p, 0)) && ln > 2 {
132132
firstLine := extractLineFromFormattedErrorMessage(formatted, ln-3)
133133
secondLine := extractLineFromFormattedErrorMessage(formatted, ln-2)
134134
syntaxErr = firstLine + secondLine

p/pyfunc.go

+24-26
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package p
55
*/
66
import "C"
77
import (
8-
"errors"
98
"fmt"
109
"pfi/sensorbee/sensorbee/data"
1110
"runtime"
@@ -29,7 +28,8 @@ func invokeDirect(pyObj *C.PyObject, name string, args ...data.Value) (Object, e
2928
go func() {
3029
defer func() {
3130
if r := recover(); r != nil {
32-
ch <- &Result{Object{}, fmt.Errorf("cannot call '%v' due to panic: %v", name, r)}
31+
ch <- &Result{Object{}, fmt.Errorf(
32+
"cannot call '%v' due to panic: %v", name, r)}
3333
}
3434
}()
3535

@@ -40,32 +40,25 @@ func invokeDirect(pyObj *C.PyObject, name string, args ...data.Value) (Object, e
4040
var res Object
4141
pyFunc, err := getPyFunc(pyObj, name)
4242
if err != nil {
43-
ch <- &Result{res, fmt.Errorf("%v at '%v'", err.Error(), name)}
43+
ch <- &Result{res, fmt.Errorf("fail to get '%v' function: %v", name,
44+
err.Error())}
4445
return
4546
}
4647
defer pyFunc.decRef()
4748

48-
pyArg := C.PyTuple_New(C.Py_ssize_t(len(args)))
49-
if pyArg == nil {
50-
ch <- &Result{res, getPyErr()}
49+
pyArg, err := convertArgsGo2Py(args)
50+
if err != nil {
51+
ch <- &Result{res, fmt.Errorf(
52+
"fail to convert argument in calling '%v' function: %v",
53+
name, err.Error())}
5154
return
5255
}
53-
defer C.Py_DecRef(pyArg)
54-
55-
for i, v := range args {
56-
o, err := newPyObj(v)
57-
if err != nil {
58-
ch <- &Result{res, fmt.Errorf("%v at '%v'", err.Error(), name)}
59-
return
60-
}
61-
// PyTuple object takes over the value's reference, and not need to
62-
// decrease reference counter.
63-
C.PyTuple_SetItem(pyArg, C.Py_ssize_t(i), o.p)
64-
}
56+
defer pyArg.decRef()
6557

66-
ret, err := pyFunc.callObject(Object{p: pyArg})
58+
ret, err := pyFunc.callObject(pyArg)
6759
if err != nil {
68-
ch <- &Result{res, fmt.Errorf("%v in '%v'", err.Error(), name)}
60+
ch <- &Result{res, fmt.Errorf("fail to call '%v' function: %v", name,
61+
err.Error())}
6962
return
7063
}
7164

@@ -86,7 +79,8 @@ func invoke(pyObj *C.PyObject, name string, args ...data.Value) (data.Value, err
8679
go func() {
8780
defer func() {
8881
if r := recover(); r != nil {
89-
ch <- &Result{data.Null{}, fmt.Errorf("cannot call '%v' due to panic: %v", name, r)}
82+
ch <- &Result{data.Null{}, fmt.Errorf(
83+
"cannot call '%v' due to panic: %v", name, r)}
9084
}
9185
}()
9286

@@ -97,21 +91,25 @@ func invoke(pyObj *C.PyObject, name string, args ...data.Value) (data.Value, err
9791
var res data.Value
9892
pyFunc, err := getPyFunc(pyObj, name)
9993
if err != nil {
100-
ch <- &Result{res, fmt.Errorf("%v at '%v'", err.Error(), name)}
94+
ch <- &Result{res, fmt.Errorf("fail to get '%v' function: %v", name,
95+
err.Error())}
10196
return
10297
}
10398
defer pyFunc.decRef()
10499

105100
pyArg, err := convertArgsGo2Py(args)
106101
if err != nil {
107-
ch <- &Result{res, fmt.Errorf("%v at '%v'", err.Error(), name)}
102+
ch <- &Result{res, fmt.Errorf(
103+
"fail to convert argument in calling '%v' function: %v",
104+
name, err.Error())}
108105
return
109106
}
110107
defer pyArg.decRef()
111108

112109
ret, err := pyFunc.callObject(pyArg)
113110
if err != nil {
114-
ch <- &Result{res, fmt.Errorf("%v in '%v'", err.Error(), name)}
111+
ch <- &Result{res, fmt.Errorf("fail to call '%v' function: %v", name,
112+
err.Error())}
115113
return
116114
}
117115
defer ret.decRef()
@@ -131,11 +129,11 @@ func getPyFunc(pyObj *C.PyObject, name string) (ObjectFunc, error) {
131129

132130
pyFunc := C.PyObject_GetAttrString(pyObj, cFunc)
133131
if pyFunc == nil {
134-
return ObjectFunc{}, errors.New("cannot load function")
132+
return ObjectFunc{}, getPyErr()
135133
}
136134

137135
if ok := C.PyCallable_Check(pyFunc); ok == 0 {
138-
return ObjectFunc{}, errors.New("cannot call function")
136+
return ObjectFunc{}, fmt.Errorf("'%v' is not callable object", name)
139137
}
140138

141139
return ObjectFunc{Object{p: pyFunc}}, nil

p/pyfunc_test.go

+41
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,47 @@ import (
77
"testing"
88
)
99

10+
func TestGetPyFuncError(t *testing.T) {
11+
Convey("Given an initialized pyfunc test module", t, func() {
12+
ImportSysAndAppendPath("")
13+
14+
mdl, err := LoadModule("_test_pyfunc")
15+
So(err, ShouldBeNil)
16+
So(mdl, ShouldNotBeNil)
17+
Reset(func() {
18+
mdl.DecRef()
19+
})
20+
21+
Convey("When func name is not exist", func() {
22+
_, err := safePythonCall(func() (Object, error) {
23+
o, err := getPyFunc(mdl.p, "not_exit_func")
24+
if err != nil {
25+
return Object{}, err
26+
}
27+
// this code is illegal, should not cast ObjectFunc to Object,
28+
// but this function is only to confirm error
29+
return Object{p: o.p}, nil
30+
})
31+
So(err, ShouldNotBeNil)
32+
So(err.Error(), ShouldContainSubstring, "AttributeError")
33+
})
34+
35+
Convey("When given name does not indicate function attribute", func() {
36+
_, err := safePythonCall(func() (Object, error) {
37+
o, err := getPyFunc(mdl.p, "not_func_attr")
38+
if err != nil {
39+
return Object{}, err
40+
}
41+
// this code is illegal, should not cast ObjectFunc to Object,
42+
// but this function is only to confirm error
43+
return Object{p: o.p}, nil
44+
})
45+
So(err, ShouldNotBeNil)
46+
So(err.Error(), ShouldContainSubstring, "not callable")
47+
})
48+
})
49+
}
50+
1051
func TestPyFunc(t *testing.T) {
1152
Convey("Given an initialized pyfunc test module", t, func() {
1253
ImportSysAndAppendPath("")

p/pymodule.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ func LoadModule(name string) (ObjectModule, error) {
3232
state := GILState_Ensure()
3333
defer GILState_Release(state)
3434

35-
pyMdl, err := C.PyImport_ImportModule(cModule)
35+
pyMdl := C.PyImport_ImportModule(cModule)
3636
if pyMdl == nil {
37-
ch <- &Result{ObjectModule{}, fmt.Errorf("cannot load '%v' module: %v",
38-
name, err)}
37+
ch <- &Result{ObjectModule{}, fmt.Errorf(
38+
"fail to load '%v' module: %v", name, getPyErr())}
3939
return
4040
}
4141

@@ -118,8 +118,8 @@ func (m *ObjectModule) GetClass(name string) (ObjectInstance, error) {
118118

119119
pyInstance := C.PyObject_GetAttrString(m.p, cName)
120120
if pyInstance == nil {
121-
ch <- &Result{ObjectInstance{}, fmt.Errorf("cannot get '%v' instance",
122-
name)}
121+
ch <- &Result{ObjectInstance{}, fmt.Errorf(
122+
"fail to get '%v' instance: %v", name, getPyErr())}
123123
return
124124
}
125125
ch <- &Result{ObjectInstance{Object{p: pyInstance}}, nil}

p/pymodule_test.go

+32
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ import (
66
"testing"
77
)
88

9+
func TestLoadModuleError(t *testing.T) {
10+
Convey("Given a python interpreter set up default import path", t, func() {
11+
ImportSysAndAppendPath("")
12+
13+
Convey("When get module with not exist module name", func() {
14+
_, err := LoadModule("not_exist_module")
15+
Convey("Then an error should be occurred", func() {
16+
So(err, ShouldNotBeNil)
17+
So(err.Error(), ShouldContainSubstring, "ImportError")
18+
})
19+
})
20+
21+
Convey("When get syntax error module", func() {
22+
_, err := LoadModule("_test_syntax_error")
23+
Convey("Then an error should be occurred", func() {
24+
So(err, ShouldNotBeNil)
25+
So(err.Error(), ShouldContainSubstring, "SyntaxError")
26+
})
27+
})
28+
})
29+
}
30+
931
func TestNewInstanceAndStateness(t *testing.T) {
1032
Convey("Given an initialized python module", t, func() {
1133

@@ -19,6 +41,7 @@ func TestNewInstanceAndStateness(t *testing.T) {
1941
_, err := mdl.NewInstance("NonexistentClass")
2042
Convey("Then an error should be occurred", func() {
2143
So(err, ShouldNotBeNil)
44+
So(err.Error(), ShouldContainSubstring, "AttributeError")
2245
})
2346
})
2447

@@ -115,6 +138,7 @@ func TestNewInstanceAndStateness(t *testing.T) {
115138
_, err := mdl.NewInstance("PythonTest", params)
116139
Convey("Then an error should be occurred", func() {
117140
So(err, ShouldNotBeNil)
141+
So(err.Error(), ShouldContainSubstring, "TypeError")
118142
})
119143
})
120144

@@ -158,6 +182,14 @@ func TestNewInstanceAndStateness(t *testing.T) {
158182
So(actual, ShouldEqual, "instance_value")
159183
})
160184
})
185+
186+
Convey("When get an invalid class instance by GetClass", func() {
187+
_, err := mdl.GetClass("NonexistentClass")
188+
Convey("Then an error should be occurred", func() {
189+
So(err, ShouldNotBeNil)
190+
So(err.Error(), ShouldContainSubstring, "AttributeError")
191+
})
192+
})
161193
})
162194
}
163195

0 commit comments

Comments
 (0)