add module-import tests to most of runtime package (#125)

This commit is contained in:
Daniel 2019-03-01 15:55:29 -08:00 committed by GitHub
parent 0c5e80b057
commit 53ce12998e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 132 deletions

View file

@ -1,4 +1,4 @@
package objects package objects
// CallableFunc is a function signature for the callable functions. // CallableFunc is a function signature for the callable functions.
type CallableFunc func(args ...Object) (ret Object, err error) type CallableFunc = func(args ...Object) (ret Object, err error)

View file

@ -253,8 +253,6 @@ func FromInterface(v interface{}) (Object, error) {
return v, nil return v, nil
case CallableFunc: case CallableFunc:
return &UserFunction{Value: v}, nil return &UserFunction{Value: v}, nil
case func(...Object) (Object, error):
return &UserFunction{Value: v}, nil
} }
return nil, fmt.Errorf("cannot convert to object: %T", v) return nil, fmt.Errorf("cannot convert to object: %T", v)

View file

@ -11,6 +11,7 @@ func TestCondExpr(t *testing.T) {
expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, 4) expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, 4)
expect(t, ` expect(t, `
out = 0
f1 := func() { out += 10 } f1 := func() { out += 10 }
f2 := func() { out = -out } f2 := func() { out = -out }
true ? f1() : f2() true ? f1() : f2()

View file

@ -6,20 +6,20 @@ import (
func TestForIn(t *testing.T) { func TestForIn(t *testing.T) {
// array // array
expect(t, `for x in [1, 2, 3] { out += x }`, 6) // value expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, 6) // value
expect(t, `for i, x in [1, 2, 3] { out += i + x }`, 9) // index, value expect(t, `out = 0; for i, x in [1, 2, 3] { out += i + x }`, 9) // index, value
expect(t, `func() { for i, x in [1, 2, 3] { out += i + x } }()`, 9) // index, value expect(t, `out = 0; func() { for i, x in [1, 2, 3] { out += i + x } }()`, 9) // index, value
expect(t, `for i, _ in [1, 2, 3] { out += i }`, 3) // index, _ expect(t, `out = 0; for i, _ in [1, 2, 3] { out += i }`, 3) // index, _
expect(t, `func() { for i, _ in [1, 2, 3] { out += i } }()`, 3) // index, _ expect(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, 3) // index, _
// map // map
expect(t, `for v in {a:2,b:3,c:4} { out += v }`, 9) // value expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, 9) // value
expect(t, `for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, "b") // key, value expect(t, `out = ""; for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, "b") // key, value
expect(t, `for k, _ in {a:2} { out += k }`, "a") // key, _ expect(t, `out = ""; for k, _ in {a:2} { out += k }`, "a") // key, _
expect(t, `for _, v in {a:2,b:3,c:4} { out += v }`, 9) // _, value expect(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, 9) // _, value
expect(t, `func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, "b") // key, value expect(t, `out = ""; func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, "b") // key, value
// string // string
expect(t, `for c in "abcde" { out += c }`, "abcde") expect(t, `out = ""; for c in "abcde" { out += c }`, "abcde")
expect(t, `for i, c in "abcde" { if i == 2 { continue }; out += c }`, "abde") expect(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, "abde")
} }

View file

@ -6,6 +6,7 @@ import (
func TestFor(t *testing.T) { func TestFor(t *testing.T) {
expect(t, ` expect(t, `
out = 0
for { for {
out++ out++
if out == 5 { if out == 5 {
@ -14,6 +15,7 @@ func TestFor(t *testing.T) {
}`, 5) }`, 5)
expect(t, ` expect(t, `
out = 0
for { for {
out++ out++
if out == 5 { if out == 5 {
@ -22,6 +24,7 @@ func TestFor(t *testing.T) {
}`, 5) }`, 5)
expect(t, ` expect(t, `
out = 0
a := 0 a := 0
for { for {
a++ a++
@ -31,6 +34,7 @@ func TestFor(t *testing.T) {
}`, 7) // 1 + 2 + 4 }`, 7) // 1 + 2 + 4
expect(t, ` expect(t, `
out = 0
a := 0 a := 0
for { for {
a++ a++
@ -40,6 +44,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5 }`, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0
for true { for true {
out++ out++
if out == 5 { if out == 5 {
@ -58,6 +63,7 @@ func TestFor(t *testing.T) {
out = a`, 5) out = a`, 5)
expect(t, ` expect(t, `
out = 0
a := 0 a := 0
for true { for true {
a++ a++
@ -67,6 +73,7 @@ func TestFor(t *testing.T) {
}`, 7) // 1 + 2 + 4 }`, 7) // 1 + 2 + 4
expect(t, ` expect(t, `
out = 0
a := 0 a := 0
for true { for true {
a++ a++
@ -76,6 +83,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5 }`, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0
func() { func() {
for true { for true {
out++ out++
@ -86,11 +94,13 @@ func TestFor(t *testing.T) {
}()`, 5) }()`, 5)
expect(t, ` expect(t, `
out = 0
for a:=1; a<=10; a++ { for a:=1; a<=10; a++ {
out += a out += a
}`, 55) }`, 55)
expect(t, ` expect(t, `
out = 0
for a:=1; a<=3; a++ { for a:=1; a<=3; a++ {
for b:=3; b<=6; b++ { for b:=3; b<=6; b++ {
out += b out += b
@ -98,6 +108,7 @@ func TestFor(t *testing.T) {
}`, 54) }`, 54)
expect(t, ` expect(t, `
out = 0
func() { func() {
for { for {
out++ out++
@ -108,6 +119,7 @@ func TestFor(t *testing.T) {
}()`, 5) }()`, 5)
expect(t, ` expect(t, `
out = 0
func() { func() {
for true { for true {
out++ out++
@ -199,6 +211,7 @@ func TestFor(t *testing.T) {
out = a`, 5) out = a`, 5)
expect(t, ` expect(t, `
out = 0
for a:=1; a<=10; a++ { for a:=1; a<=10; a++ {
if a == 3 { if a == 3 {
continue continue
@ -210,6 +223,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5 }`, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0
for a:=1; a<=10; { for a:=1; a<=10; {
if a == 3 { if a == 3 {
a++ a++

View file

@ -5,8 +5,8 @@ import (
) )
func TestIncDec(t *testing.T) { func TestIncDec(t *testing.T) {
expect(t, `out++`, 1) expect(t, `out = 0; out++`, 1)
expect(t, `out--`, -1) expect(t, `out = 0; out--`, -1)
expect(t, `a := 0; a++; out = a`, 1) expect(t, `a := 0; a++; out = a`, 1)
expect(t, `a := 0; a++; a--; out = a`, 0) expect(t, `a := 0; a++; a--; out = a`, 0)

View file

@ -193,7 +193,7 @@ export func() {
}) })
// 'export' statement is ignored outside module // 'export' statement is ignored outside module
expect(t, `a := 5; export func() { a = 10 }(); out = a`, 5) expectNoMod(t, `a := 5; export func() { a = 10 }(); out = a`, 5)
// 'export' must be in the top-level // 'export' must be in the top-level
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{

View file

@ -30,68 +30,31 @@ func expect(t *testing.T, input string, expected interface{}) {
expectWithUserModules(t, input, expected, nil) expectWithUserModules(t, input, expected, nil)
} }
func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) { func expectNoMod(t *testing.T, input string, expected interface{}) {
// parse runVM(t, input, expected, nil, nil, true)
file := parse(t, input) }
if file == nil {
return
}
// compiler/VM func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) {
runVM(t, file, expected, symbols, nil) runVM(t, input, expected, symbols, nil, true)
} }
func expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) { func expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) {
// parse runVM(t, input, expected, nil, userModules, false)
file := parse(t, input)
if file == nil {
return
}
// compiler/VM
runVM(t, file, expected, nil, userModules)
} }
func expectError(t *testing.T, input, expected string) { func expectError(t *testing.T, input, expected string) {
expected = strings.TrimSpace(expected) runVMError(t, input, nil, nil, expected)
if expected == "" {
panic("expected must not be empty")
}
expectErrorWithUserModules(t, input, nil, expected)
} }
func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) { func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) {
// parse runVMError(t, input, nil, userModules, expected)
program := parse(t, input)
if program == nil {
return
}
// compiler/VM
_, trace, err := traceCompileRun(program, nil, userModules)
if !assert.Error(t, err) ||
!assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n"))
}
} }
func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) { func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) {
// parse runVMError(t, input, symbols, nil, expected)
program := parse(t, input)
if program == nil {
return
}
// compiler/VM
_, trace, err := traceCompileRun(program, symbols, nil)
if !assert.Error(t, err) ||
!assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n"))
}
} }
func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[string]objects.Object, userModules map[string]string) (ok bool) { func runVM(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object, userModules map[string]string, skipModuleTest bool) {
expectedObj := toObject(expected) expectedObj := toObject(expected)
if symbols == nil { if symbols == nil {
@ -99,81 +62,68 @@ func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[strin
} }
symbols[testOut] = objectZeroCopy(expectedObj) symbols[testOut] = objectZeroCopy(expectedObj)
res, trace, err := traceCompileRun(file, symbols, userModules) // first pass: run the code normally
{
// parse
file := parse(t, input)
if file == nil {
return
}
defer func() { // compiler/VM
if !ok { res, trace, err := traceCompileRun(file, symbols, userModules)
if !assert.NoError(t, err) ||
!assert.Equal(t, expectedObj, res[testOut]) {
t.Log("\n" + strings.Join(trace, "\n")) t.Log("\n" + strings.Join(trace, "\n"))
} }
}() }
if !assert.NoError(t, err) { // second pass: run the code as import module
if !skipModuleTest {
file := parse(t, `out = import("__code__")`)
if file == nil {
return
}
expectedObj := toObject(expected)
switch eo := expectedObj.(type) {
case *objects.Array:
expectedObj = &objects.ImmutableArray{Value: eo.Value}
case *objects.Map:
expectedObj = &objects.ImmutableMap{Value: eo.Value}
}
if userModules == nil {
userModules = make(map[string]string)
}
userModules["__code__"] = fmt.Sprintf("out := undefined; %s; export out", input)
res, trace, err := traceCompileRun(file, symbols, userModules)
if !assert.NoError(t, err) ||
!assert.Equal(t, expectedObj, res[testOut]) {
t.Log("\n" + strings.Join(trace, "\n"))
}
}
}
func runVMError(t *testing.T, input string, symbols map[string]objects.Object, userModules map[string]string, expected string) {
expected = strings.TrimSpace(expected)
if expected == "" {
panic("expected must not be empty")
}
// parse
program := parse(t, input)
if program == nil {
return return
} }
ok = assert.Equal(t, expectedObj, res[testOut]) // compiler/VM
_, trace, err := traceCompileRun(program, symbols, userModules)
return if !assert.Error(t, err) ||
} !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n"))
func errorObject(v interface{}) *objects.Error {
return &objects.Error{Value: toObject(v)}
}
func toObject(v interface{}) objects.Object {
switch v := v.(type) {
case objects.Object:
return v
case string:
return &objects.String{Value: v}
case int64:
return &objects.Int{Value: v}
case int: // for convenience
return &objects.Int{Value: int64(v)}
case bool:
if v {
return objects.TrueValue
}
return objects.FalseValue
case rune:
return &objects.Char{Value: v}
case byte: // for convenience
return &objects.Char{Value: rune(v)}
case float64:
return &objects.Float{Value: v}
case []byte:
return &objects.Bytes{Value: v}
case MAP:
objs := make(map[string]objects.Object)
for k, v := range v {
objs[k] = toObject(v)
}
return &objects.Map{Value: objs}
case ARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, toObject(e))
}
return &objects.Array{Value: objs}
case IMAP:
objs := make(map[string]objects.Object)
for k, v := range v {
objs[k] = toObject(v)
}
return &objects.ImmutableMap{Value: objs}
case IARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, toObject(e))
}
return &objects.ImmutableArray{Value: objs}
} }
panic(fmt.Errorf("unknown type: %T", v))
} }
type tracer struct { type tracer struct {
@ -296,6 +246,66 @@ func parse(t *testing.T, input string) *ast.File {
return file return file
} }
func errorObject(v interface{}) *objects.Error {
return &objects.Error{Value: toObject(v)}
}
func toObject(v interface{}) objects.Object {
switch v := v.(type) {
case objects.Object:
return v
case string:
return &objects.String{Value: v}
case int64:
return &objects.Int{Value: v}
case int: // for convenience
return &objects.Int{Value: int64(v)}
case bool:
if v {
return objects.TrueValue
}
return objects.FalseValue
case rune:
return &objects.Char{Value: v}
case byte: // for convenience
return &objects.Char{Value: rune(v)}
case float64:
return &objects.Float{Value: v}
case []byte:
return &objects.Bytes{Value: v}
case MAP:
objs := make(map[string]objects.Object)
for k, v := range v {
objs[k] = toObject(v)
}
return &objects.Map{Value: objs}
case ARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, toObject(e))
}
return &objects.Array{Value: objs}
case IMAP:
objs := make(map[string]objects.Object)
for k, v := range v {
objs[k] = toObject(v)
}
return &objects.ImmutableMap{Value: objs}
case IARR:
var objs []objects.Object
for _, e := range v {
objs = append(objs, toObject(e))
}
return &objects.ImmutableArray{Value: objs}
}
panic(fmt.Errorf("unknown type: %T", v))
}
func objectZeroCopy(o objects.Object) objects.Object { func objectZeroCopy(o objects.Object) objects.Object {
switch o.(type) { switch o.(type) {
case *objects.Int: case *objects.Int: