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
// 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
case CallableFunc:
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)

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 = 0
f1 := func() { out += 10 }
f2 := func() { out = -out }
true ? f1() : f2()

View file

@ -6,20 +6,20 @@ import (
func TestForIn(t *testing.T) {
// array
expect(t, `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, `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, `func() { for i, _ in [1, 2, 3] { out += i } }()`, 3) // index, _
expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, 6) // value
expect(t, `out = 0; 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, `out = 0; 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
expect(t, `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, `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, `func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, "b") // key, value
expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, 9) // 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, `out = ""; for k, _ in {a:2} { out += k }`, "a") // key, _
expect(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, 9) // _, 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
expect(t, `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 c in "abcde" { out += c }`, "abcde")
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) {
expect(t, `
out = 0
for {
out++
if out == 5 {
@ -14,6 +15,7 @@ func TestFor(t *testing.T) {
}`, 5)
expect(t, `
out = 0
for {
out++
if out == 5 {
@ -22,6 +24,7 @@ func TestFor(t *testing.T) {
}`, 5)
expect(t, `
out = 0
a := 0
for {
a++
@ -31,6 +34,7 @@ func TestFor(t *testing.T) {
}`, 7) // 1 + 2 + 4
expect(t, `
out = 0
a := 0
for {
a++
@ -40,6 +44,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5
expect(t, `
out = 0
for true {
out++
if out == 5 {
@ -58,6 +63,7 @@ func TestFor(t *testing.T) {
out = a`, 5)
expect(t, `
out = 0
a := 0
for true {
a++
@ -67,6 +73,7 @@ func TestFor(t *testing.T) {
}`, 7) // 1 + 2 + 4
expect(t, `
out = 0
a := 0
for true {
a++
@ -76,6 +83,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5
expect(t, `
out = 0
func() {
for true {
out++
@ -86,11 +94,13 @@ func TestFor(t *testing.T) {
}()`, 5)
expect(t, `
out = 0
for a:=1; a<=10; a++ {
out += a
}`, 55)
expect(t, `
out = 0
for a:=1; a<=3; a++ {
for b:=3; b<=6; b++ {
out += b
@ -98,6 +108,7 @@ func TestFor(t *testing.T) {
}`, 54)
expect(t, `
out = 0
func() {
for {
out++
@ -108,6 +119,7 @@ func TestFor(t *testing.T) {
}()`, 5)
expect(t, `
out = 0
func() {
for true {
out++
@ -199,6 +211,7 @@ func TestFor(t *testing.T) {
out = a`, 5)
expect(t, `
out = 0
for a:=1; a<=10; a++ {
if a == 3 {
continue
@ -210,6 +223,7 @@ func TestFor(t *testing.T) {
}`, 12) // 1 + 2 + 4 + 5
expect(t, `
out = 0
for a:=1; a<=10; {
if a == 3 {
a++

View file

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

View file

@ -193,7 +193,7 @@ export func() {
})
// '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
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)
}
func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) {
// parse
file := parse(t, input)
if file == nil {
return
}
func expectNoMod(t *testing.T, input string, expected interface{}) {
runVM(t, input, expected, nil, nil, true)
}
// compiler/VM
runVM(t, file, expected, symbols, nil)
func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) {
runVM(t, input, expected, symbols, nil, true)
}
func expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) {
// parse
file := parse(t, input)
if file == nil {
return
}
// compiler/VM
runVM(t, file, expected, nil, userModules)
runVM(t, input, expected, nil, userModules, false)
}
func expectError(t *testing.T, input, expected string) {
expected = strings.TrimSpace(expected)
if expected == "" {
panic("expected must not be empty")
}
expectErrorWithUserModules(t, input, nil, expected)
runVMError(t, input, nil, nil, expected)
}
func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) {
// parse
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"))
}
runVMError(t, input, nil, userModules, expected)
}
func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) {
// parse
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"))
}
runVMError(t, input, symbols, nil, expected)
}
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)
if symbols == nil {
@ -99,81 +62,68 @@ func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[strin
}
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() {
if !ok {
// compiler/VM
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"))
}
}()
}
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
}
ok = assert.Equal(t, expectedObj, res[testOut])
return
}
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}
// compiler/VM
_, trace, err := traceCompileRun(program, symbols, 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"))
}
panic(fmt.Errorf("unknown type: %T", v))
}
type tracer struct {
@ -296,6 +246,66 @@ func parse(t *testing.T, input string) *ast.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 {
switch o.(type) {
case *objects.Int: