260 lines
7 KiB
Go
260 lines
7 KiB
Go
package runtime_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/d5/tengo/objects"
|
|
)
|
|
|
|
func TestStdLib(t *testing.T) {
|
|
// stdlib
|
|
expect(t, `math := import("math"); out = math.abs(1)`, 1.0)
|
|
expect(t, `math := import("math"); out = math.abs(-1)`, 1.0)
|
|
expect(t, `math := import("math"); out = math.abs(1.0)`, 1.0)
|
|
expect(t, `math := import("math"); out = math.abs(-1.0)`, 1.0)
|
|
|
|
// os.File
|
|
expect(t, `
|
|
os := import("os")
|
|
|
|
write_file := func(filename, data) {
|
|
file := os.create(filename)
|
|
if !file { return file }
|
|
|
|
if res := file.write(bytes(data)); is_error(res) {
|
|
return res
|
|
}
|
|
|
|
return file.close()
|
|
}
|
|
|
|
read_file := func(filename) {
|
|
file := os.open(filename)
|
|
if !file { return file }
|
|
|
|
data := bytes(100)
|
|
cnt := file.read(data)
|
|
if is_error(cnt) {
|
|
return cnt
|
|
}
|
|
|
|
file.close()
|
|
return data[:cnt]
|
|
}
|
|
|
|
if write_file("./temp", "foobar") {
|
|
out = string(read_file("./temp"))
|
|
}
|
|
|
|
os.remove("./temp")
|
|
`, "foobar")
|
|
|
|
// exec.command
|
|
expect(t, `
|
|
os := import("os")
|
|
cmd := os.exec("echo", "foo", "bar")
|
|
if !is_error(cmd) {
|
|
out = cmd.output()
|
|
}
|
|
`, []byte("foo bar\n"))
|
|
|
|
}
|
|
|
|
func TestUserModules(t *testing.T) {
|
|
// user modules
|
|
|
|
// export none
|
|
expectWithUserModules(t, `out = import("mod1")`, objects.UndefinedValue, map[string]string{
|
|
"mod1": `fn := func() { return 5.0 }; a := 2`,
|
|
})
|
|
|
|
// export values
|
|
expectWithUserModules(t, `out = import("mod1")`, 5, map[string]string{
|
|
"mod1": `export 5`,
|
|
})
|
|
expectWithUserModules(t, `out = import("mod1")`, "foo", map[string]string{
|
|
"mod1": `export "foo"`,
|
|
})
|
|
|
|
// export composite types
|
|
expectWithUserModules(t, `out = import("mod1")`, IARR{1, 2, 3}, map[string]string{
|
|
"mod1": `export [1, 2, 3]`,
|
|
})
|
|
expectWithUserModules(t, `out = import("mod1")`, IMAP{"a": 1, "b": 2}, map[string]string{
|
|
"mod1": `export {a: 1, b: 2}`,
|
|
})
|
|
// export value is immutable
|
|
expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{
|
|
"mod1": `export {a: 1, b: 2}`,
|
|
}, "not index-assignable")
|
|
expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{
|
|
"mod1": `export [1, 2, 3]`,
|
|
}, "not index-assignable")
|
|
|
|
// code after export statement will not be executed
|
|
expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{
|
|
"mod1": `a := 10; export a; a = 20`,
|
|
})
|
|
expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{
|
|
"mod1": `a := 10; export a; a = 20; export a`,
|
|
})
|
|
|
|
// export function
|
|
expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{
|
|
"mod1": `export func() { return 5.0 }`,
|
|
})
|
|
// export function that reads module-global variable
|
|
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{
|
|
"mod1": `a := 1.5; export func() { return a + 5.0 }`,
|
|
})
|
|
// export function that read local variable
|
|
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{
|
|
"mod1": `export func() { a := 1.5; return a + 5.0 }`,
|
|
})
|
|
// export function that read free variables
|
|
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{
|
|
"mod1": `export func() { a := 1.5; return func() { return a + 5.0 }() }`,
|
|
})
|
|
|
|
// recursive function in module
|
|
expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{
|
|
"mod1": `
|
|
a := func(x) {
|
|
return x == 0 ? 0 : x + a(x-1)
|
|
}
|
|
|
|
export a(5)
|
|
`})
|
|
expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{
|
|
"mod1": `
|
|
export func() {
|
|
a := func(x) {
|
|
return x == 0 ? 0 : x + a(x-1)
|
|
}
|
|
|
|
return a(5)
|
|
}()
|
|
`})
|
|
|
|
// (main) -> mod1 -> mod2
|
|
expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{
|
|
"mod1": `export import("mod2")`,
|
|
"mod2": `export func() { return 5.0 }`,
|
|
})
|
|
// (main) -> mod1 -> mod2
|
|
// -> mod2
|
|
expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{
|
|
"mod1": `export import("mod2")`,
|
|
"mod2": `export func() { return 5.0 }`,
|
|
})
|
|
// (main) -> mod1 -> mod2 -> mod3
|
|
// -> mod2 -> mod3
|
|
expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{
|
|
"mod1": `export import("mod2")`,
|
|
"mod2": `export import("mod3")`,
|
|
"mod3": `export func() { return 5.0 }`,
|
|
})
|
|
|
|
// cyclic imports
|
|
// (main) -> mod1 -> mod2 -> mod1
|
|
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
|
|
"mod1": `import("mod2")`,
|
|
"mod2": `import("mod1")`,
|
|
}, "Compile Error: cyclic module import: mod1\n\tat mod2:1:1")
|
|
// (main) -> mod1 -> mod2 -> mod3 -> mod1
|
|
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
|
|
"mod1": `import("mod2")`,
|
|
"mod2": `import("mod3")`,
|
|
"mod3": `import("mod1")`,
|
|
}, "Compile Error: cyclic module import: mod1\n\tat mod3:1:1")
|
|
// (main) -> mod1 -> mod2 -> mod3 -> mod2
|
|
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
|
|
"mod1": `import("mod2")`,
|
|
"mod2": `import("mod3")`,
|
|
"mod3": `import("mod2")`,
|
|
}, "Compile Error: cyclic module import: mod2\n\tat mod3:1:1")
|
|
|
|
// unknown modules
|
|
expectErrorWithUserModules(t, `import("mod0")`, map[string]string{
|
|
"mod1": `a := 5`,
|
|
}, "module 'mod0' not found")
|
|
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
|
|
"mod1": `import("mod2")`,
|
|
}, "module 'mod2' not found")
|
|
|
|
// module is immutable but its variables is not necessarily immutable.
|
|
expectWithUserModules(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, 5, map[string]string{
|
|
"mod1": `export {a: {b: 3}}`,
|
|
})
|
|
|
|
// make sure module has same builtin functions
|
|
expectWithUserModules(t, `out = import("mod1")`, "int", map[string]string{
|
|
"mod1": `export func() { return type_name(0) }()`,
|
|
})
|
|
|
|
// 'export' statement is ignored outside module
|
|
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{
|
|
"mod1": `func() { export 5 }()`,
|
|
}, "Compile Error: export not allowed inside function\n\tat mod1:1:10")
|
|
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
|
|
"mod1": `func() { func() { export 5 }() }()`,
|
|
}, "Compile Error: export not allowed inside function\n\tat mod1:1:19")
|
|
|
|
// module cannot access outer scope
|
|
expectErrorWithUserModules(t, `a := 5; import("mod1")`, map[string]string{
|
|
"mod1": `export a`,
|
|
}, "Compile Error: unresolved reference 'a'\n\tat mod1:1:8")
|
|
|
|
// runtime error within modules
|
|
expectErrorWithUserModules(t, `
|
|
a := 1;
|
|
b := import("mod1");
|
|
b(a)`,
|
|
map[string]string{"mod1": `
|
|
export func(a) {
|
|
a()
|
|
}
|
|
`,
|
|
}, "Runtime Error: not callable: int\n\tat mod1:3:4\n\tat test:4:1")
|
|
}
|
|
|
|
func TestModuleBlockScopes(t *testing.T) {
|
|
// block scopes in module
|
|
expectWithUserModules(t, `out = import("mod1")()`, 1, map[string]string{
|
|
"mod1": `
|
|
rand := import("rand")
|
|
foo := func() { return 1 }
|
|
export func() {
|
|
rand.intn(3)
|
|
return foo()
|
|
}
|
|
`,
|
|
})
|
|
|
|
expectWithUserModules(t, `out = import("mod1")()`, 10, map[string]string{
|
|
"mod1": `
|
|
rand := import("rand")
|
|
foo := func() { return 1 }
|
|
export func() {
|
|
rand.intn(3)
|
|
if foo() {}
|
|
return 10
|
|
}
|
|
`,
|
|
})
|
|
|
|
expectWithUserModules(t, `out = import("mod1")()`, 10, map[string]string{
|
|
"mod1": `
|
|
rand := import("rand")
|
|
foo := func() { return 1 }
|
|
export func() {
|
|
rand.intn(3)
|
|
if true { foo() }
|
|
return 10
|
|
}
|
|
`,
|
|
})
|
|
}
|