test: refactor eval tests (#111)

evalCheck is renamed in eval.

Add testCase struct to describe test cases. A "pre" function can be
invoked prior to run the eval script which generate the result.

A runTests function executes all tests in a testCase array.  Parallel
sub-tests execution  is disabled, as simultaneous compile operations
in the same interpreter context is not yet safe.

Many simple tests can now be describe in one-liners.
This commit is contained in:
Marc Vertes
2019-02-27 13:29:27 +01:00
committed by Ludovic Fernandez
parent f60fe68471
commit 80f20189b4
2 changed files with 175 additions and 168 deletions

View File

@@ -2,10 +2,8 @@ package interp_test
import (
"fmt"
"log"
"net/http"
"reflect"
"strconv"
"testing"
"time"
@@ -13,141 +11,148 @@ import (
"github.com/containous/dyngo/stdlib"
)
func TestEval0(t *testing.T) {
// testCase represents an interpreter test case.
// Care must be taken when defining multiple test cases within the same interpreter
// context, as all declarations occur in the global scope and are therefore
// shared between multiple test cases.
// Hint: use different variables or package names in testcases to keep them uncoupled.
type testCase struct {
desc, src, res, err string
skip string // if not empty, skip this test case (used in case of known error)
pre func() // functions to execute prior eval src, or nil
}
func TestEvalArithmetic(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `var I int = 2`)
t1 := evalCheck(t, i, `I`)
if t1.Interface().(int) != 2 {
t.Fatalf("expected 2, got %v", t1)
}
runTests(t, i, []testCase{
{desc: "add_II", src: "2 + 3", res: "5"},
{desc: "add_FI", src: "2.3 + 3", res: "5.3"},
{desc: "add_IF", src: "2 + 3.3", res: "5.3"},
{desc: "mul_II", src: "2 * 3", res: "6"},
{desc: "mul_FI", src: "2.2 * 3", res: "6.6000000000000005"},
{desc: "mul_IF", src: "3 * 2.2", res: "6.6000000000000005"},
})
}
func TestEval1(t *testing.T) {
func TestEvalDecl(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `func Hello() string { return "hello" }`)
v := evalCheck(t, i, `Hello`)
f, ok := v.Interface().(func() string)
if !ok {
t.Fatal("conversion failed")
}
if s := f(); s != "hello" {
t.Fatalf("expected hello, got %v", s)
}
runTests(t, i, []testCase{
{pre: func() { eval(t, i, "var i int = 2") }, src: "i", res: "2"},
{pre: func() { eval(t, i, "var j, k int = 2, 3") }, src: "j", res: "2"},
{pre: func() { eval(t, i, "var l, m int = 2, 3") }, src: "k", res: "3"},
{pre: func() { eval(t, i, "func f() int {return 4}") }, src: "f()", res: "4"},
{pre: func() { eval(t, i, `package foo; var I = 2`) }, src: "foo.I", res: "2"},
{pre: func() { eval(t, i, `package foo; func F() int {return 5}`) }, src: "foo.F()", res: "5"},
})
}
func TestEval2(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `package foo; var I int = 2`)
t1 := evalCheck(t, i, `foo.I`)
if t1.Interface().(int) != 2 {
t.Fatalf("expected 2, got %v", t1)
}
}
func TestEval3(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `package foo; func Hello() string { return "hello" }`)
v := evalCheck(t, i, `foo.Hello`)
f, ok := v.Interface().(func() string)
if !ok {
t.Fatal("conversion failed")
}
if s := f(); s != "hello" {
t.Fatalf("expected hello, got %v", s)
}
}
func TestEvalNil0(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `func getNil() error { return nil }`)
v := evalCheck(t, i, `getNil()`)
if !v.IsNil() {
t.Fatalf("expected nil, got %v", v)
}
}
func TestEvalNil1(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `
package bar
func New() func(string) error {
return func(v string) error {
return nil
}
}
`)
v := evalCheck(t, i, `bar.New()`)
fn, ok := v.Interface().(func(string) error)
if !ok {
t.Fatal("conversion failed")
}
if res := fn("hello"); res != nil {
t.Fatalf("expected nil, got %v", res)
}
}
func TestEvalNil2(t *testing.T) {
i := interp.New(interp.Opt{})
_, err := i.Eval(`a := nil`)
if err.Error() != "1:27: use of untyped nil" {
t.Fatal("should have failed")
}
}
func TestEvalNil3(t *testing.T) {
log.SetFlags(log.Lshortfile)
func TestEvalImport(t *testing.T) {
i := interp.New(interp.Opt{})
i.Use(stdlib.Value, stdlib.Type)
evalCheck(t, i, `
import "fmt"
type Foo struct{}
func Hello() *Foo {
fmt.Println("Hello")
return nil
runTests(t, i, []testCase{
{pre: func() { eval(t, i, `import "time"`) }, src: "2 * time.Second", res: "2s"},
})
}
`)
evalCheck(t, i, `Hello()`)
func TestEvalNil(t *testing.T) {
i := interp.New(interp.Opt{})
i.Use(stdlib.Value, stdlib.Type)
runTests(t, i, []testCase{
{desc: "assign nil", src: "a := nil", err: "1:27: use of untyped nil"},
{desc: "return nil", pre: func() { eval(t, i, "func getNil() error {return nil}") }, src: "getNil()", res: "<nil>"},
{
desc: "return func which return nil error",
pre: func() {
eval(t, i, `
package bar
func New() func(string) error {
return func(v string) error {
return nil
}
}
`)
v := eval(t, i, `bar.New()`)
fn, ok := v.Interface().(func(string) error)
if !ok {
t.Fatal("conversion failed")
}
if res := fn("hello"); res != nil {
t.Fatalf("got %v, want nil", res)
}
},
},
{
desc: "return nil pointer",
pre: func() {
eval(t, i, `
import "fmt"
type Foo struct{}
func Hello() *Foo {
fmt.Println("Hello")
return nil
}
`)
},
src: "Hello()",
res: "<invalid reflect.Value>",
},
})
}
func TestEvalStruct0(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `
type Fromage struct {
Name string
Call func(string) string
}
runTests(t, i, []testCase{
{
desc: "func field in struct",
pre: func() {
eval(t, i, `
type Fromage struct {
Name string
Call func(string) string
}
func f() string {
a := Fromage{}
a.Name = "test"
a.Call = func(s string) string { return s }
func f() string {
a := Fromage{}
a.Name = "test"
a.Call = func(s string) string { return s }
return a.Call(a.Name)
}
`)
return a.Call(a.Name)
}
`)
},
src: "f()",
res: "test",
},
{
desc: "literal func field in struct",
pre: func() {
eval(t, i, `
type Fromage2 struct {
Name string
Call func(string) string
}
v := evalCheck(t, i, `f()`)
if v.Interface().(string) != "test" {
t.Fatalf("expected test, got %v", v)
}
func f2() string {
a := Fromage2{
"test",
func(s string) string { return s },
}
return a.Call(a.Name)
}
`)
},
src: "f2()",
res: "test",
},
})
}
func TestEvalStruct1(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `
eval(t, i, `
type Fromage struct {
Name string
Call func(string) string
@@ -163,15 +168,15 @@ func f() string {
}
`)
v := evalCheck(t, i, `f()`)
v := eval(t, i, `f()`)
if v.Interface().(string) != "test" {
t.Fatalf("expected test, got %v", v)
t.Fatalf("got %v, want test", v)
}
}
func TestEvalComposite0(t *testing.T) {
i := interp.New(interp.Opt{})
evalCheck(t, i, `
eval(t, i, `
type T struct {
a, b, c, d, e, f, g, h, i, j, k, l, m, n string
o map[string]int
@@ -183,16 +188,16 @@ var a = T{
p: []string{"hello", "world"},
}
`)
v := evalCheck(t, i, `a.p[1]`)
v := eval(t, i, `a.p[1]`)
if v.Interface().(string) != "world" {
t.Fatalf("expected world, got %v", v)
t.Fatalf("got %v, want word", v)
}
}
func TestEvalCompositeBin0(t *testing.T) {
i := interp.New(interp.Opt{})
i.Use(stdlib.Value, stdlib.Type)
evalCheck(t, i, `
eval(t, i, `
import (
"fmt"
"net/http"
@@ -204,63 +209,65 @@ func Foo() {
}
`)
http.DefaultClient = &http.Client{}
evalCheck(t, i, `Foo()`)
eval(t, i, `Foo()`)
if http.DefaultClient.Timeout != 2*time.Second {
t.Fatalf("expected 2s, got %v", http.DefaultClient.Timeout)
t.Fatalf("got %v, want 2s", http.DefaultClient.Timeout)
}
}
func TestEvalComparison(t *testing.T) {
i := interp.New(interp.Opt{})
testCases := []struct {
src string
expectedError string
expectedRes string
}{
runTests(t, i, []testCase{
{src: `"hhh" > "ggg"`, res: "true"},
{
`
type Foo string
type Bar string
desc: "mismatched types",
src: `
type Foo string
type Bar string
var a = Foo("test")
var b = Bar("test")
var c = a == b
`, "7:9: mismatched types _.Foo and _.Bar", "",
var a = Foo("test")
var b = Bar("test")
var c = a == b
`,
err: "7:13: mismatched types _.Foo and _.Bar",
},
{`"hhh" > "ggg"`, "", "true"},
}
})
}
for index, test := range testCases {
test := test
t.Run(strconv.Itoa(index), func(t *testing.T) {
t.Parallel()
func TestEvalCompositeArray(t *testing.T) {
i := interp.New(interp.Opt{})
runTests(t, i, []testCase{
{src: "a := []int{1, 2, 7: 20, 30}", res: "[1 2 0 0 0 0 0 20 30]"},
})
}
assertEval(t, i, test.src, test.expectedError, test.expectedRes)
func TestEvalUnary(t *testing.T) {
i := interp.New(interp.Opt{})
runTests(t, i, []testCase{
{src: "a := -1", res: "-1"},
{src: "b := +1", res: "1", skip: "BUG"},
{src: "c := !false", res: "true"},
})
}
func runTests(t *testing.T, i *interp.Interpreter, tests []testCase) {
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
if test.skip != "" {
t.Skip(test.skip)
}
if test.pre != nil {
test.pre()
}
if test.src != "" {
assertEval(t, i, test.src, test.err, test.res)
}
})
}
}
func TestEvalCompositeArray0(t *testing.T) {
i := interp.New(interp.Opt{})
v := evalCheck(t, i, `a := []int{1, 2, 7: 20, 30}`)
expected := "[1 2 0 0 0 0 0 20 30]"
if fmt.Sprintf("%v", v) != expected {
t.Fatalf("expected: %s, got %v", expected, v)
}
}
func TestEvalUnary0(t *testing.T) {
i := interp.New(interp.Opt{})
v := evalCheck(t, i, `a := -1`)
if expected := "-1"; fmt.Sprintf("%v", v) != expected {
t.Fatalf("Expected %v, got %v", expected, v)
}
}
func evalCheck(t *testing.T, i *interp.Interpreter, src string) reflect.Value {
func eval(t *testing.T, i *interp.Interpreter, src string) reflect.Value {
t.Helper()
res, err := i.Eval(src)
if err != nil {
t.Fatal(err)
@@ -272,7 +279,7 @@ func assertEval(t *testing.T, i *interp.Interpreter, src, expectedError, expecte
res, err := i.Eval(src)
if expectedError != "" {
if err == nil || err != nil && err.Error() != expectedError {
if err == nil || err.Error() != expectedError {
t.Fatalf("got %v, want %s", err, expectedError)
}
return

View File

@@ -35,7 +35,7 @@ func Hi(h Helloer) {
// the interfaces and methods definitions)
//
type Wrap struct {
DoHello func() // implements Hello()
DoHello func() // related to the Hello() method.
// Other interface method wrappers...
}
@@ -43,14 +43,14 @@ func (w Wrap) Hello() { w.DoHello() }
func TestInterface(t *testing.T) {
i := interp.New(interp.Opt{})
// export the Wrap type to the interpeter under virtual "wrap" package
// export the Wrap type to the interpreter under virtual "wrap" package
i.Use(nil, interp.LibTypeMap{
"wrap": {
"Wrap": reflect.TypeOf((*Wrap)(nil)).Elem(),
},
})
evalCheck(t, i, `
eval(t, i, `
import "wrap"
type MyInt int
@@ -62,7 +62,7 @@ func NewMyInt(i int) wrap.Wrap {
return wrap.Wrap{DoHello: m.Hello}
}
`)
NewMyInt := evalCheck(t, i, "NewMyInt").Interface().(func(int) Wrap)
NewMyInt := eval(t, i, "NewMyInt").Interface().(func(int) Wrap)
w := NewMyInt(4)
Hi(w)
}