793 lines
12 KiB
Go
793 lines
12 KiB
Go
package compiler_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestEntryPointWithMethod(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
|
|
func Main(op string) int {
|
|
if op == "a" {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
evalWithArgs(t, src, []byte("a"), nil, big.NewInt(1))
|
|
}
|
|
|
|
func TestEntryPointWithArgs(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
|
|
func Main(args []interface{}) int {
|
|
return 2 + args[1].(int)
|
|
}
|
|
`
|
|
args := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBigInteger(big.NewInt(1))}
|
|
evalWithArgs(t, src, nil, args, big.NewInt(3))
|
|
}
|
|
|
|
func TestEntryPointWithMethodAndArgs(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
|
|
func Main(method string, args []interface{}) int {
|
|
if method == "foobar" {
|
|
return 2 + args[1].(int)
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
args := []stackitem.Item{stackitem.NewBigInteger(big.NewInt(0)), stackitem.NewBigInteger(big.NewInt(1))}
|
|
evalWithArgs(t, src, []byte("foobar"), args, big.NewInt(3))
|
|
}
|
|
|
|
func TestArrayFieldInStruct(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
|
|
type Bar struct {
|
|
arr []int
|
|
}
|
|
|
|
func Main() int {
|
|
b := Bar{
|
|
arr: []int{0, 1, 2},
|
|
}
|
|
x := b.arr[2]
|
|
return x + 2
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(4))
|
|
}
|
|
|
|
func TestArrayItemGetIndexBinaryExpr(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := 1
|
|
y := []int{0, 1, 2}
|
|
return y[x + 1]
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(2))
|
|
}
|
|
|
|
func TestArrayItemGetIndexIdent(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := 1
|
|
y := []int{0, 1, 2}
|
|
return y[x]
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(1))
|
|
}
|
|
|
|
func TestArrayItemBinExpr(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := []int{0, 1, 2}
|
|
return x[1] + 10
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(11))
|
|
}
|
|
|
|
func TestArrayItemReturn(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
arr := []int{0, 1, 2}
|
|
return arr[1]
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(1))
|
|
}
|
|
|
|
func TestArrayItemAssign(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
arr := []int{1, 2, 3}
|
|
y := arr[0]
|
|
return y
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(1))
|
|
}
|
|
|
|
func TestStringArray(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() []string {
|
|
x := []string{"foo", "bar", "foobar"}
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, []stackitem.Item{
|
|
stackitem.NewByteArray([]byte("foo")),
|
|
stackitem.NewByteArray([]byte("bar")),
|
|
stackitem.NewByteArray([]byte("foobar")),
|
|
})
|
|
}
|
|
|
|
func TestIntArray(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() []int {
|
|
arr := []int{1, 2, 3}
|
|
return arr
|
|
}
|
|
`
|
|
eval(t, src, []stackitem.Item{
|
|
stackitem.NewBigInteger(big.NewInt(1)),
|
|
stackitem.NewBigInteger(big.NewInt(2)),
|
|
stackitem.NewBigInteger(big.NewInt(3)),
|
|
})
|
|
}
|
|
|
|
func TestArrayLen(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
arr := []int{0, 1, 2}
|
|
return len(arr)
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(3))
|
|
}
|
|
|
|
func TestStringLen(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
str := "this is medium sized string"
|
|
return len(str)
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(27))
|
|
}
|
|
|
|
func TestByteArrayLen(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
|
|
func Main() int {
|
|
b := []byte{0x00, 0x01, 0x2}
|
|
return len(b)
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(3))
|
|
}
|
|
|
|
func TestSimpleString(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() string {
|
|
x := "NEO"
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, stackitem.NewByteArray([]byte("NEO")).Value())
|
|
}
|
|
|
|
func TestBoolAssign(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() bool {
|
|
x := true
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, true)
|
|
}
|
|
|
|
func TestBoolCompare(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := true
|
|
if x {
|
|
return 10
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(10))
|
|
}
|
|
|
|
func TestBoolCompareVerbose(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := true
|
|
if x == true {
|
|
return 10
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(10))
|
|
}
|
|
|
|
func TestUnaryExpr(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() bool {
|
|
x := false
|
|
return !x
|
|
}
|
|
`
|
|
eval(t, src, true)
|
|
}
|
|
|
|
func TestIfUnaryInvertPass(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := false
|
|
if !x {
|
|
return 10
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(10))
|
|
}
|
|
|
|
func TestIfUnaryInvert(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := true
|
|
if !x {
|
|
return 10
|
|
}
|
|
return 0
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(0))
|
|
}
|
|
|
|
func TestAppendByte(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() []byte {
|
|
arr := []byte{0x00, 0x01, 0x02}
|
|
arr = append(arr, 0x03)
|
|
arr = append(arr, 0x04)
|
|
arr = append(arr, 0x05)
|
|
arr = append(arr, 0x06)
|
|
return arr
|
|
}
|
|
`
|
|
eval(t, src, []uint8{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06})
|
|
}
|
|
|
|
func TestAppendByteToEmpty(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() []byte {
|
|
out := []byte{}
|
|
out = append(out, 1)
|
|
out = append(out, 2)
|
|
return out
|
|
}`
|
|
eval(t, src, []byte{1, 2})
|
|
}
|
|
|
|
func TestAppendString(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() string {
|
|
arr := []string{"a", "b", "c"}
|
|
arr = append(arr, "d")
|
|
return arr[3]
|
|
}
|
|
`
|
|
eval(t, src, stackitem.NewByteArray([]byte("d")).Value())
|
|
}
|
|
|
|
func TestAppendInt(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
arr := []int{0, 1, 2}
|
|
arr = append(arr, 3)
|
|
return arr[3]
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(3))
|
|
}
|
|
|
|
func TestClassicForLoop(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := 0
|
|
for i := 0; i < 10; i++ {
|
|
x = i
|
|
}
|
|
return x
|
|
}
|
|
`
|
|
eval(t, src, big.NewInt(9))
|
|
}
|
|
|
|
func TestInc(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := 0
|
|
x++
|
|
return x
|
|
}
|
|
`
|
|
|
|
eval(t, src, big.NewInt(1))
|
|
}
|
|
|
|
func TestDec(t *testing.T) {
|
|
src := `
|
|
package foo
|
|
func Main() int {
|
|
x := 2
|
|
x--
|
|
return x
|
|
}
|
|
`
|
|
|
|
eval(t, src, big.NewInt(1))
|
|
}
|
|
|
|
var forLoopTestCases = []testCase{
|
|
{
|
|
"empty for loop",
|
|
`func F%d() int {
|
|
x := 0
|
|
for {
|
|
x++
|
|
if x == 2 { break }
|
|
}
|
|
return x
|
|
}
|
|
`,
|
|
big.NewInt(2),
|
|
}, {
|
|
"big iteration count",
|
|
`func F%d() int {
|
|
x := 0
|
|
for i := 0; i < 100000; i++ {
|
|
x = i
|
|
}
|
|
return x
|
|
}
|
|
`,
|
|
big.NewInt(99999),
|
|
},
|
|
{
|
|
"no init",
|
|
`func F%d() int {
|
|
i := 0
|
|
for ; i < 10; i++ {
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(10),
|
|
},
|
|
{
|
|
"no post",
|
|
`func F%d() int {
|
|
i := 0
|
|
for i < 10 {
|
|
i++
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(10),
|
|
},
|
|
{
|
|
"range",
|
|
`func F%d() int {
|
|
sum := 0
|
|
arr := []int{1, 2, 3}
|
|
for i := range arr {
|
|
sum += arr[i]
|
|
}
|
|
return sum
|
|
}
|
|
`,
|
|
big.NewInt(6),
|
|
},
|
|
{
|
|
"range, global index",
|
|
`func F%d() int {
|
|
sum := 0
|
|
i := 0
|
|
arr := []int{1, 2, 3}
|
|
for i = range arr {
|
|
sum += arr[i]
|
|
}
|
|
return sum + i
|
|
}
|
|
`,
|
|
big.NewInt(8),
|
|
},
|
|
{
|
|
"range, change variable",
|
|
`func F%d() int {
|
|
sum := 0
|
|
arr := []int{1, 2, 3}
|
|
for i := range arr {
|
|
sum += arr[i]
|
|
i++
|
|
sum += i
|
|
}
|
|
return sum
|
|
}
|
|
`,
|
|
big.NewInt(12),
|
|
},
|
|
{
|
|
"break",
|
|
`func F%d() int {
|
|
var i int
|
|
for i < 10 {
|
|
i++
|
|
if i == 5 {
|
|
break
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(5),
|
|
},
|
|
{
|
|
"break label",
|
|
`func F%d() int {
|
|
var i int
|
|
loop:
|
|
for i < 10 {
|
|
i++
|
|
if i == 5 {
|
|
break loop
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(5),
|
|
},
|
|
{
|
|
"nested break",
|
|
`func F%d() int {
|
|
var i int
|
|
for i < 10 {
|
|
i++
|
|
for j := 0; j < 2; j++ {
|
|
i++
|
|
if i == 5 {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(11),
|
|
},
|
|
{
|
|
"nested break label",
|
|
`func F%d() int {
|
|
var i int
|
|
loop:
|
|
for i < 10 {
|
|
i++
|
|
for j := 0; j < 2; j++ {
|
|
if i == 5 {
|
|
break loop
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(5),
|
|
},
|
|
{
|
|
"continue",
|
|
`func F%d() int {
|
|
var i, j int
|
|
for i < 10 {
|
|
i++
|
|
if i >= 5 {
|
|
continue
|
|
}
|
|
j++
|
|
}
|
|
return j
|
|
}
|
|
`,
|
|
big.NewInt(4),
|
|
},
|
|
{
|
|
"continue label",
|
|
`func F%d() int {
|
|
var i, j int
|
|
loop:
|
|
for i < 10 {
|
|
i++
|
|
if i >= 5 {
|
|
continue loop
|
|
}
|
|
j++
|
|
}
|
|
return j
|
|
}
|
|
`,
|
|
big.NewInt(4),
|
|
},
|
|
{
|
|
"nested continue",
|
|
`func F%d() int {
|
|
var i, k int
|
|
for i < 10 {
|
|
i++
|
|
for j := 0; j < 3; j++ {
|
|
if j >= 2 {
|
|
continue
|
|
}
|
|
k++
|
|
}
|
|
}
|
|
return k
|
|
}
|
|
`,
|
|
big.NewInt(20),
|
|
},
|
|
{
|
|
"nested continue label",
|
|
`func F%d() int {
|
|
var i int
|
|
loop:
|
|
for ; i < 10; i += 10 {
|
|
i++
|
|
for j := 0; j < 4; j++ {
|
|
if i == 5 {
|
|
continue loop
|
|
}
|
|
i++
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(15),
|
|
},
|
|
{
|
|
"range break",
|
|
`func F%d() int {
|
|
var i int
|
|
arr := []int{1, 2, 3}
|
|
for i = range arr {
|
|
if arr[i] == 2 {
|
|
break
|
|
}
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(1),
|
|
},
|
|
{
|
|
"range nested break",
|
|
`func F%d() int {
|
|
k := 5
|
|
arr := []int{1, 2, 3}
|
|
urr := []int{4, 5, 6, 7}
|
|
loop:
|
|
for range arr {
|
|
k++
|
|
for j := range urr {
|
|
k++
|
|
if j == 3 {
|
|
break loop
|
|
}
|
|
}
|
|
}
|
|
return k
|
|
}
|
|
`,
|
|
big.NewInt(10),
|
|
},
|
|
{
|
|
"range continue",
|
|
`func F%d() int {
|
|
i := 6
|
|
arr := []int{1, 2, 3}
|
|
for j := range arr {
|
|
if arr[j] < 2 {
|
|
continue
|
|
}
|
|
i++
|
|
}
|
|
return i
|
|
}
|
|
`,
|
|
big.NewInt(8),
|
|
},
|
|
{
|
|
"range, no variable",
|
|
`func F%d() int {
|
|
sum := 0
|
|
arr := []int{1, 2, 3}
|
|
for range arr {
|
|
sum += 1
|
|
}
|
|
return sum
|
|
}
|
|
`,
|
|
big.NewInt(3),
|
|
},
|
|
{
|
|
"range value",
|
|
`func f(a int) int { return a }
|
|
func F%d() int {
|
|
var sum int
|
|
arr := []int{1, 9, 4}
|
|
for _, v := range arr {
|
|
sum += f(v)
|
|
}
|
|
return sum
|
|
}
|
|
`,
|
|
big.NewInt(14),
|
|
},
|
|
{
|
|
"range, map",
|
|
`func F%d() int {
|
|
m := map[int]int{
|
|
1: 13,
|
|
11: 17,
|
|
}
|
|
var sum int
|
|
for i, v := range m {
|
|
sum += i
|
|
sum += v
|
|
}
|
|
return sum
|
|
}
|
|
`,
|
|
big.NewInt(42),
|
|
},
|
|
{
|
|
"range, type conversion",
|
|
`type intArr []int
|
|
func F%d() int {
|
|
a := []int{1, 2, 3}
|
|
s := 0
|
|
for _, v := range intArr(a) {
|
|
s += v
|
|
}
|
|
return s
|
|
}
|
|
`,
|
|
big.NewInt(6),
|
|
},
|
|
}
|
|
|
|
func TestForLoop(t *testing.T) {
|
|
srcBuilder := bytes.NewBuffer([]byte("package testcase\n"))
|
|
for i, tc := range forLoopTestCases {
|
|
srcBuilder.WriteString(fmt.Sprintf(tc.src, i))
|
|
}
|
|
|
|
ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, tc := range forLoopTestCases {
|
|
v := vm.New()
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
v.Reset(trigger.Application)
|
|
invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
|
|
runAndCheck(t, v, tc.result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestForLoopComplexConditions(t *testing.T) {
|
|
forCondTestCases := []struct {
|
|
Name string
|
|
Cond string
|
|
Assign string
|
|
Result int64
|
|
}{
|
|
{Cond: "i < 3 && j < 2", Result: 2},
|
|
{Cond: "i < 3 || j < 2", Result: 3},
|
|
{Cond: "i < 3 && (j < 2 || i < 1)", Result: 2},
|
|
{Cond: "i < 3 && (j < 2 && i < 1)", Result: 1},
|
|
{Cond: "(i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
|
|
{Cond: "(i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
|
|
{Cond: "ok", Assign: "ok = i < 3 && j < 2", Result: 2},
|
|
{Cond: "ok", Assign: "ok = i < 3 || j < 2", Result: 3},
|
|
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 || i < 1)", Result: 2},
|
|
{Cond: "ok", Assign: "ok = i < 3 && (j < 2 && i < 1)", Result: 1},
|
|
{Cond: "ok", Assign: "ok = (i < 1 || j < 3) && (i < 3 || j < 1)", Result: 3},
|
|
{Cond: "ok", Assign: "ok = (i < 2 && j < 4) || (i < 4 && j < 2)", Result: 2},
|
|
}
|
|
|
|
tmpl := `func F%d() int {
|
|
var ok bool
|
|
_ = ok
|
|
i := 0
|
|
j := 0
|
|
%s
|
|
for %s {
|
|
i++
|
|
j++
|
|
%s
|
|
}
|
|
return i
|
|
}
|
|
`
|
|
srcBuilder := bytes.NewBufferString("package foo\n")
|
|
for i, tc := range forCondTestCases {
|
|
srcBuilder.WriteString(fmt.Sprintf(tmpl, i, tc.Assign, tc.Cond, tc.Assign))
|
|
}
|
|
|
|
ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
|
|
require.NoError(t, err)
|
|
|
|
for i, tc := range forCondTestCases {
|
|
v := vm.New()
|
|
name := tc.Cond
|
|
if tc.Assign != "" {
|
|
name = tc.Assign
|
|
}
|
|
t.Run(name, func(t *testing.T) {
|
|
v.Reset(trigger.Application)
|
|
invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
|
|
runAndCheck(t, v, big.NewInt(tc.Result))
|
|
})
|
|
}
|
|
}
|