neo-go/pkg/compiler/binary_expr_test.go

403 lines
7.4 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/stretchr/testify/require"
)
var binaryExprTestCases = []testCase{
{
"simple add",
`func F%d() int {
x := 2 + 2
return x
}
`,
big.NewInt(4),
},
{
"simple sub",
`func F%d() int {
x := 2 - 2
return x
}
`,
big.NewInt(0),
},
{
"simple div",
`func F%d() int {
x := 2 / 2
return x
}
`,
big.NewInt(1),
},
{
"simple mod",
`func F%d() int {
x := 3 %% 2
return x
}
`,
big.NewInt(1),
},
{
"simple mul",
`func F%d() int {
x := 4 * 2
return x
}
`,
big.NewInt(8),
},
{
"simple binary expr in return",
`func F%d() int {
x := 2
return 2 + x
}
`,
big.NewInt(4),
},
{
"complex binary expr",
`func F%d() int {
x := 4
y := 8
z := x + 2 + 2 - 8
return y * z
}
`,
big.NewInt(0),
},
{
"compare not equal strings with eql",
`func F%d() int {
str := "a string"
if str == "another string" {
return 1
}
return 0
}
`,
big.NewInt(0),
},
{
"compare equal strings with eql",
`func F%d() int {
str := "a string"
if str == "a string" {
return 1
}
return 0
}
`,
big.NewInt(1),
},
{
"compare not equal strings with neq",
`func F%d() int {
str := "a string"
if str != "another string" {
return 1
}
return 0
}
`,
big.NewInt(1),
},
{
"compare equal strings with neq",
`func F%d() int {
str := "a string"
if str != "a string" {
return 1
}
return 0
}
`,
big.NewInt(0),
},
{
"compare equal ints with eql",
`func F%d() int {
x := 10
if x == 10 {
return 1
}
return 0
}
`,
big.NewInt(1),
},
{
"compare equal ints with neq",
`func F%d() int {
x := 10
if x != 10 {
return 1
}
return 0
}
`,
big.NewInt(0),
},
{
"compare not equal ints with eql",
`func F%d() int {
x := 11
if x == 10 {
return 1
}
return 0
}
`,
big.NewInt(0),
},
{
"compare not equal ints with neq",
`func F%d() int {
x := 11
if x != 10 {
return 1
}
return 0
}
`,
big.NewInt(1),
},
{
"simple add and assign",
`func F%d() int {
x := 2
x += 1
return x
}
`,
big.NewInt(3),
},
{
"simple sub and assign",
`func F%d() int {
x := 2
x -= 1
return x
}
`,
big.NewInt(1),
},
{
"simple mul and assign",
`func F%d() int {
x := 2
x *= 2
return x
}
`,
big.NewInt(4),
},
{
"simple div and assign",
`func F%d() int {
x := 2
x /= 2
return x
}
`,
big.NewInt(1),
},
{
"simple mod and assign",
`func F%d() int {
x := 5
x %%= 2
return x
}
`,
big.NewInt(1),
},
}
func TestBinaryExprs(t *testing.T) {
srcBuilder := bytes.NewBuffer([]byte("package testcase\n"))
for i, tc := range binaryExprTestCases {
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 binaryExprTestCases {
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 addBoolExprTestFunc(testCases []testCase, b *bytes.Buffer, val bool, cond string) []testCase {
n := len(testCases)
b.WriteString(fmt.Sprintf(`
func F%d_expr() int {
var cond%d = %s
if cond%d { return 42 }
return 17
}
func F%d_cond() int {
if %s { return 42 }
return 17
}
func F%d_else() int {
if %s { return 42 } else { return 17 }
}
`, n, n, cond, n, n, cond, n, cond))
res := big.NewInt(42)
if !val {
res.SetInt64(17)
}
return append(testCases, testCase{
name: cond,
result: res,
})
}
func runBooleanCases(t *testing.T, testCases []testCase, src string) {
ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(src), nil)
require.NoError(t, err)
for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("AsExpression", func(t *testing.T) {
v := vm.New()
invokeMethod(t, fmt.Sprintf("F%d_expr", i), ne.Script, v, di)
runAndCheck(t, v, tc.result)
})
t.Run("InCondition", func(t *testing.T) {
v := vm.New()
invokeMethod(t, fmt.Sprintf("F%d_cond", i), ne.Script, v, di)
runAndCheck(t, v, tc.result)
})
t.Run("InConditionWithElse", func(t *testing.T) {
v := vm.New()
invokeMethod(t, fmt.Sprintf("F%d_else", i), ne.Script, v, di)
runAndCheck(t, v, tc.result)
})
})
}
}
// TestBooleanExprs enumerates a lot of possible combinations of boolean expressions
// and tests if the result matches to that of Go.
func TestBooleanExprs(t *testing.T) {
header := `package foo
var s = "str"
var v = 9
`
srcBuilder := bytes.NewBuffer([]byte(header))
var testCases []testCase
trueExpr := []string{"true", "v < 10", "v <= 9", "v > 8", "v >= 9", "v == 9", "v != 8", `s == "str"`}
falseExpr := []string{"false", "v > 9", "v >= 10", "v < 9", "v <= 8", "v == 8", "v != 9", `s == "a"`}
t.Run("Single", func(t *testing.T) {
for _, s := range trueExpr {
testCases = addBoolExprTestFunc(testCases, srcBuilder, true, s)
}
for _, s := range falseExpr {
testCases = addBoolExprTestFunc(testCases, srcBuilder, false, s)
}
runBooleanCases(t, testCases, srcBuilder.String())
})
type arg struct {
val bool
s string
}
var double []arg
for _, e := range trueExpr {
double = append(double, arg{true, e + " || false"})
double = append(double, arg{true, e + " && true"})
}
for _, e := range falseExpr {
double = append(double, arg{false, e + " && true"})
double = append(double, arg{false, e + " || false"})
}
t.Run("Double", func(t *testing.T) {
testCases = testCases[:0]
srcBuilder.Reset()
srcBuilder.WriteString(header)
for i := range double {
testCases = addBoolExprTestFunc(testCases, srcBuilder, double[i].val, double[i].s)
}
runBooleanCases(t, testCases, srcBuilder.String())
})
var triple []arg
for _, a1 := range double {
for _, a2 := range double {
triple = append(triple, arg{a1.val || a2.val, fmt.Sprintf("(%s) || (%s)", a1.s, a2.s)})
triple = append(triple, arg{a1.val && a2.val, fmt.Sprintf("(%s) && (%s)", a1.s, a2.s)})
}
}
t.Run("Triple", func(t *testing.T) {
const step = 350 // empirically found value to make script less than 65536 in size
for start := 0; start < len(triple); start += step {
testCases = testCases[:0]
srcBuilder.Reset()
srcBuilder.WriteString(header)
for i := start; i < start+step && i < len(triple); i++ {
testCases = addBoolExprTestFunc(testCases, srcBuilder, triple[i].val, triple[i].s)
}
runBooleanCases(t, testCases, srcBuilder.String())
}
})
}
func TestShortCircuit(t *testing.T) {
srcTmpl := `package foo
var a = 1
func inc() bool { a += 1; return %s }
func Main() int {
if %s {
return 41 + a
}
return 16 + a
}`
t.Run("||", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, "true", "a == 1 || inc()")
eval(t, src, big.NewInt(42))
})
t.Run("&&", func(t *testing.T) {
src := fmt.Sprintf(srcTmpl, "false", "a == 2 && inc()")
eval(t, src, big.NewInt(17))
})
}
func TestEmitBoolean(t *testing.T) {
src := `package foo
func Main() int {
a := true
if (a == true) == true {
return 42
}
return 11
}`
eval(t, src, big.NewInt(42))
}