github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/compiler/binary_expr_test.go (about)

     1  package compiler_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"math/big"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/nspcc-dev/neo-go/pkg/compiler"
    11  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
    12  	"github.com/nspcc-dev/neo-go/pkg/vm"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  var binaryExprTestCases = []testCase{
    17  	{
    18  		"simple add",
    19  		`func F%d() int {
    20  			x := 2 + 2
    21  			return x
    22  		}
    23  		`,
    24  		big.NewInt(4),
    25  	},
    26  	{
    27  		"simple sub",
    28  		`func F%d() int {
    29  			x := 2 - 2
    30  			return x
    31  		}
    32  		`,
    33  		big.NewInt(0),
    34  	},
    35  	{
    36  		"simple div",
    37  		`func F%d() int {
    38  			x := 2 / 2
    39  			return x
    40  		}
    41  		`,
    42  		big.NewInt(1),
    43  	},
    44  	{
    45  		"simple mod",
    46  		`func F%d() int {
    47  			x := 3 %% 2
    48  			return x
    49  		}
    50  		`,
    51  		big.NewInt(1),
    52  	},
    53  	{
    54  		"simple mul",
    55  		`func F%d() int {
    56  			x := 4 * 2
    57  			return x
    58  		}
    59  		`,
    60  		big.NewInt(8),
    61  	},
    62  	{
    63  		"simple binary expr in return",
    64  		`func F%d() int {
    65  			x := 2
    66  			return 2 + x
    67  		}
    68  		`,
    69  		big.NewInt(4),
    70  	},
    71  	{
    72  		"complex binary expr",
    73  		`func F%d() int {
    74  			x := 4
    75  			y := 8
    76  			z := x + 2 + 2 - 8
    77  			return y * z
    78  		}
    79  		`,
    80  		big.NewInt(0),
    81  	},
    82  	{
    83  		"compare not equal strings with eql",
    84  		`func F%d() int {
    85  			str := "a string"
    86  			if str == "another string" {
    87  				return 1
    88  			}
    89  			return 0
    90  		}
    91  		`,
    92  		big.NewInt(0),
    93  	},
    94  	{
    95  		"compare equal strings with eql",
    96  		`func F%d() int {
    97  			str := "a string"
    98  			if str == "a string" {
    99  				return 1
   100  			}
   101  			return 0
   102  		}
   103  		`,
   104  		big.NewInt(1),
   105  	},
   106  	{
   107  		"compare not equal strings with neq",
   108  		`func F%d() int {
   109  			str := "a string"
   110  			if str != "another string" {
   111  				return 1
   112  			}
   113  			return 0
   114  		}
   115  		`,
   116  		big.NewInt(1),
   117  	},
   118  	{
   119  		"compare equal strings with neq",
   120  		`func F%d() int {
   121  			str := "a string"
   122  			if str != "a string" {
   123  				return 1
   124  			}
   125  			return 0
   126  		}
   127  		`,
   128  		big.NewInt(0),
   129  	},
   130  	{
   131  		"compare equal ints with eql",
   132  		`func F%d() int {
   133  			x := 10
   134  			if x == 10 {
   135  				return 1
   136  			}
   137  			return 0
   138  		}
   139  		`,
   140  		big.NewInt(1),
   141  	},
   142  	{
   143  		"compare equal ints with neq",
   144  		`func F%d() int {
   145  			x := 10
   146  			if x != 10 {
   147  				return 1
   148  			}
   149  			return 0
   150  		}
   151  		`,
   152  		big.NewInt(0),
   153  	},
   154  	{
   155  		"compare not equal ints with eql",
   156  		`func F%d() int {
   157  			x := 11
   158  			if x == 10 {
   159  				return 1
   160  			}
   161  			return 0
   162  		}
   163  		`,
   164  		big.NewInt(0),
   165  	},
   166  	{
   167  		"compare not equal ints with neq",
   168  		`func F%d() int {
   169  			x := 11
   170  			if x != 10 {
   171  				return 1
   172  			}
   173  			return 0
   174  		}
   175  		`,
   176  		big.NewInt(1),
   177  	},
   178  	{
   179  		"simple add and assign",
   180  		`func F%d() int {
   181  			x := 2
   182  			x += 1
   183  			return x
   184  		}
   185  		`,
   186  		big.NewInt(3),
   187  	},
   188  	{
   189  		"simple sub and assign",
   190  		`func F%d() int {
   191  			x := 2
   192  			x -= 1
   193  			return x
   194  		}
   195  		`,
   196  		big.NewInt(1),
   197  	},
   198  	{
   199  		"simple mul and assign",
   200  		`func F%d() int {
   201  			x := 2
   202  			x *= 2
   203  			return x
   204  		}
   205  		`,
   206  		big.NewInt(4),
   207  	},
   208  	{
   209  		"simple div and assign",
   210  		`func F%d() int {
   211  			x := 2
   212  			x /= 2
   213  			return x
   214  		}
   215  		`,
   216  		big.NewInt(1),
   217  	},
   218  	{
   219  		"simple mod and assign",
   220  		`func F%d() int {
   221  			x := 5
   222  			x %%= 2
   223  			return x
   224  		}
   225  		`,
   226  		big.NewInt(1),
   227  	},
   228  }
   229  
   230  func TestBinaryExprs(t *testing.T) {
   231  	srcBuilder := bytes.NewBuffer([]byte("package testcase\n"))
   232  	for i, tc := range binaryExprTestCases {
   233  		srcBuilder.WriteString(fmt.Sprintf(tc.src, i))
   234  	}
   235  
   236  	ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(srcBuilder.String()), nil)
   237  	require.NoError(t, err)
   238  
   239  	for i, tc := range binaryExprTestCases {
   240  		v := vm.New()
   241  		t.Run(tc.name, func(t *testing.T) {
   242  			v.Reset(trigger.Application)
   243  			invokeMethod(t, fmt.Sprintf("F%d", i), ne.Script, v, di)
   244  			runAndCheck(t, v, tc.result)
   245  		})
   246  	}
   247  }
   248  
   249  func addBoolExprTestFunc(testCases []testCase, b *bytes.Buffer, val bool, cond string) []testCase {
   250  	n := len(testCases)
   251  	b.WriteString(fmt.Sprintf(`
   252  	func F%d_expr() int {
   253  		var cond%d = %s
   254  		if cond%d { return 42 }
   255  		return 17
   256  	}
   257  	func F%d_cond() int {
   258  		if %s { return 42 }
   259  		return 17
   260  	}
   261  	func F%d_else() int {
   262  		if %s { return 42 } else { return 17 }
   263  	}
   264  	`, n, n, cond, n, n, cond, n, cond))
   265  
   266  	res := big.NewInt(42)
   267  	if !val {
   268  		res.SetInt64(17)
   269  	}
   270  
   271  	return append(testCases, testCase{
   272  		name:   cond,
   273  		result: res,
   274  	})
   275  }
   276  
   277  func runBooleanCases(t *testing.T, testCases []testCase, src string) {
   278  	ne, di, err := compiler.CompileWithOptions("file.go", strings.NewReader(src), nil)
   279  	require.NoError(t, err)
   280  
   281  	for i, tc := range testCases {
   282  		t.Run(tc.name, func(t *testing.T) {
   283  			t.Run("AsExpression", func(t *testing.T) {
   284  				v := vm.New()
   285  				invokeMethod(t, fmt.Sprintf("F%d_expr", i), ne.Script, v, di)
   286  				runAndCheck(t, v, tc.result)
   287  			})
   288  			t.Run("InCondition", func(t *testing.T) {
   289  				v := vm.New()
   290  				invokeMethod(t, fmt.Sprintf("F%d_cond", i), ne.Script, v, di)
   291  				runAndCheck(t, v, tc.result)
   292  			})
   293  			t.Run("InConditionWithElse", func(t *testing.T) {
   294  				v := vm.New()
   295  				invokeMethod(t, fmt.Sprintf("F%d_else", i), ne.Script, v, di)
   296  				runAndCheck(t, v, tc.result)
   297  			})
   298  		})
   299  	}
   300  }
   301  
   302  // TestBooleanExprs enumerates a lot of possible combinations of boolean expressions
   303  // and tests if the result matches to that of Go.
   304  func TestBooleanExprs(t *testing.T) {
   305  	header := `package foo
   306  	var s = "str"
   307  	var v = 9
   308  	`
   309  
   310  	srcBuilder := bytes.NewBuffer([]byte(header))
   311  
   312  	var testCases []testCase
   313  
   314  	trueExpr := []string{"true", "v < 10", "v <= 9", "v > 8", "v >= 9", "v == 9", "v != 8", `s == "str"`}
   315  	falseExpr := []string{"false", "v > 9", "v >= 10", "v < 9", "v <= 8", "v == 8", "v != 9", `s == "a"`}
   316  	t.Run("Single", func(t *testing.T) {
   317  		for _, s := range trueExpr {
   318  			testCases = addBoolExprTestFunc(testCases, srcBuilder, true, s)
   319  		}
   320  		for _, s := range falseExpr {
   321  			testCases = addBoolExprTestFunc(testCases, srcBuilder, false, s)
   322  		}
   323  		runBooleanCases(t, testCases, srcBuilder.String())
   324  	})
   325  
   326  	type arg struct {
   327  		val bool
   328  		s   string
   329  	}
   330  
   331  	var double []arg
   332  	for _, e := range trueExpr {
   333  		double = append(double, arg{true, e + " || false"})
   334  		double = append(double, arg{true, e + " && true"})
   335  	}
   336  	for _, e := range falseExpr {
   337  		double = append(double, arg{false, e + " && true"})
   338  		double = append(double, arg{false, e + " || false"})
   339  	}
   340  
   341  	t.Run("Double", func(t *testing.T) {
   342  		testCases = testCases[:0]
   343  		srcBuilder.Reset()
   344  		srcBuilder.WriteString(header)
   345  		for i := range double {
   346  			testCases = addBoolExprTestFunc(testCases, srcBuilder, double[i].val, double[i].s)
   347  		}
   348  		runBooleanCases(t, testCases, srcBuilder.String())
   349  	})
   350  
   351  	var triple []arg
   352  	for _, a1 := range double {
   353  		for _, a2 := range double {
   354  			triple = append(triple, arg{a1.val || a2.val, fmt.Sprintf("(%s) || (%s)", a1.s, a2.s)})
   355  			triple = append(triple, arg{a1.val && a2.val, fmt.Sprintf("(%s) && (%s)", a1.s, a2.s)})
   356  		}
   357  	}
   358  
   359  	t.Run("Triple", func(t *testing.T) {
   360  		const step = 350 // empirically found value to make script less than 65536 in size
   361  		for start := 0; start < len(triple); start += step {
   362  			testCases = testCases[:0]
   363  			srcBuilder.Reset()
   364  			srcBuilder.WriteString(header)
   365  			for i := start; i < start+step && i < len(triple); i++ {
   366  				testCases = addBoolExprTestFunc(testCases, srcBuilder, triple[i].val, triple[i].s)
   367  			}
   368  			runBooleanCases(t, testCases, srcBuilder.String())
   369  		}
   370  	})
   371  }
   372  
   373  func TestShortCircuit(t *testing.T) {
   374  	srcTmpl := `package foo
   375  	var a = 1
   376  	func inc() bool { a += 1; return %s }
   377  	func Main() int {
   378  		if %s {
   379  			return 41 + a
   380  		}
   381  		return 16 + a
   382  	}`
   383  	t.Run("||", func(t *testing.T) {
   384  		src := fmt.Sprintf(srcTmpl, "true", "a == 1 || inc()")
   385  		eval(t, src, big.NewInt(42))
   386  	})
   387  	t.Run("&&", func(t *testing.T) {
   388  		src := fmt.Sprintf(srcTmpl, "false", "a == 2 && inc()")
   389  		eval(t, src, big.NewInt(17))
   390  	})
   391  }
   392  
   393  func TestEmitBoolean(t *testing.T) {
   394  	src := `package foo
   395  	func Main() int {
   396  		a := true
   397  		if (a == true) == true {
   398  			return 42
   399  		}
   400  		return 11
   401  	}`
   402  	eval(t, src, big.NewInt(42))
   403  }