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 }