github.com/shawnclovie/gopher-lua@v0.0.0-20200520092726-90b44ec0e2f2/state_test.go (about) 1 package lua 2 3 import ( 4 "context" 5 "strings" 6 "testing" 7 "time" 8 ) 9 10 func TestCallStackOverflowWhenFixed(t *testing.T) { 11 L := NewState(Options{ 12 CallStackSize: 3, 13 }) 14 defer L.Close() 15 16 // expect fixed stack implementation by default (for backwards compatibility) 17 stack := L.stack 18 if _, ok := stack.(*fixedCallFrameStack); !ok { 19 t.Errorf("expected fixed callframe stack by default") 20 } 21 22 errorIfScriptNotFail(t, L, ` 23 local function recurse(count) 24 if count > 0 then 25 recurse(count - 1) 26 end 27 end 28 local function c() 29 print(_printregs()) 30 recurse(9) 31 end 32 c() 33 `, "stack overflow") 34 } 35 36 func TestCallStackOverflowWhenAutoGrow(t *testing.T) { 37 L := NewState(Options{ 38 CallStackSize: 3, 39 MinimizeStackMemory: true, 40 }) 41 defer L.Close() 42 43 // expect auto growing stack implementation when MinimizeStackMemory is set 44 stack := L.stack 45 if _, ok := stack.(*autoGrowingCallFrameStack); !ok { 46 t.Errorf("expected fixed callframe stack by default") 47 } 48 49 errorIfScriptNotFail(t, L, ` 50 local function recurse(count) 51 if count > 0 then 52 recurse(count - 1) 53 end 54 end 55 local function c() 56 print(_printregs()) 57 recurse(9) 58 end 59 c() 60 `, "stack overflow") 61 } 62 63 func TestSkipOpenLibs(t *testing.T) { 64 L := NewState(Options{SkipOpenLibs: true}) 65 defer L.Close() 66 errorIfScriptNotFail(t, L, `print("")`, 67 "attempt to call a non-function object") 68 L2 := NewState() 69 defer L2.Close() 70 errorIfScriptFail(t, L2, `print("")`) 71 } 72 73 func TestGetAndReplace(t *testing.T) { 74 L := NewState() 75 defer L.Close() 76 L.Push(LString("a")) 77 L.Replace(1, LString("b")) 78 L.Replace(0, LString("c")) 79 errorIfNotEqual(t, LNil, L.Get(0)) 80 errorIfNotEqual(t, LNil, L.Get(-10)) 81 errorIfNotEqual(t, L.Env, L.Get(EnvironIndex)) 82 errorIfNotEqual(t, LString("b"), L.Get(1)) 83 L.Push(LString("c")) 84 L.Push(LString("d")) 85 L.Replace(-2, LString("e")) 86 errorIfNotEqual(t, LString("e"), L.Get(-2)) 87 registry := L.NewTable() 88 L.Replace(RegistryIndex, registry) 89 L.G.Registry = registry 90 errorIfGFuncNotFail(t, L, func(L *LState) int { 91 L.Replace(RegistryIndex, LNil) 92 return 0 93 }, "registry must be a table") 94 errorIfGFuncFail(t, L, func(L *LState) int { 95 env := L.NewTable() 96 L.Replace(EnvironIndex, env) 97 errorIfNotEqual(t, env, L.Get(EnvironIndex)) 98 return 0 99 }) 100 errorIfGFuncNotFail(t, L, func(L *LState) int { 101 L.Replace(EnvironIndex, LNil) 102 return 0 103 }, "environment must be a table") 104 errorIfGFuncFail(t, L, func(L *LState) int { 105 gbl := L.NewTable() 106 L.Replace(GlobalsIndex, gbl) 107 errorIfNotEqual(t, gbl, L.G.Global) 108 return 0 109 }) 110 errorIfGFuncNotFail(t, L, func(L *LState) int { 111 L.Replace(GlobalsIndex, LNil) 112 return 0 113 }, "_G must be a table") 114 115 L2 := NewState() 116 defer L2.Close() 117 clo := L2.NewClosure(func(L2 *LState) int { 118 L2.Replace(UpvalueIndex(1), LNumber(3)) 119 errorIfNotEqual(t, LNumber(3), L2.Get(UpvalueIndex(1))) 120 return 0 121 }, LNumber(1), LNumber(2)) 122 L2.SetGlobal("clo", clo) 123 errorIfScriptFail(t, L2, `clo()`) 124 } 125 126 func TestRemove(t *testing.T) { 127 L := NewState() 128 defer L.Close() 129 L.Push(LString("a")) 130 L.Push(LString("b")) 131 L.Push(LString("c")) 132 133 L.Remove(4) 134 errorIfNotEqual(t, LString("a"), L.Get(1)) 135 errorIfNotEqual(t, LString("b"), L.Get(2)) 136 errorIfNotEqual(t, LString("c"), L.Get(3)) 137 errorIfNotEqual(t, 3, L.GetTop()) 138 139 L.Remove(3) 140 errorIfNotEqual(t, LString("a"), L.Get(1)) 141 errorIfNotEqual(t, LString("b"), L.Get(2)) 142 errorIfNotEqual(t, LNil, L.Get(3)) 143 errorIfNotEqual(t, 2, L.GetTop()) 144 L.Push(LString("c")) 145 146 L.Remove(-10) 147 errorIfNotEqual(t, LString("a"), L.Get(1)) 148 errorIfNotEqual(t, LString("b"), L.Get(2)) 149 errorIfNotEqual(t, LString("c"), L.Get(3)) 150 errorIfNotEqual(t, 3, L.GetTop()) 151 152 L.Remove(2) 153 errorIfNotEqual(t, LString("a"), L.Get(1)) 154 errorIfNotEqual(t, LString("c"), L.Get(2)) 155 errorIfNotEqual(t, LNil, L.Get(3)) 156 errorIfNotEqual(t, 2, L.GetTop()) 157 } 158 159 func TestToInt(t *testing.T) { 160 L := NewState() 161 defer L.Close() 162 L.Push(LNumber(10)) 163 L.Push(LString("99.9")) 164 L.Push(L.NewTable()) 165 errorIfNotEqual(t, 10, L.ToInt(1)) 166 errorIfNotEqual(t, 99, L.ToInt(2)) 167 errorIfNotEqual(t, 0, L.ToInt(3)) 168 } 169 170 func TestToInt64(t *testing.T) { 171 L := NewState() 172 defer L.Close() 173 L.Push(LNumber(10)) 174 L.Push(LString("99.9")) 175 L.Push(L.NewTable()) 176 errorIfNotEqual(t, int64(10), L.ToInt64(1)) 177 errorIfNotEqual(t, int64(99), L.ToInt64(2)) 178 errorIfNotEqual(t, int64(0), L.ToInt64(3)) 179 } 180 181 func TestToNumber(t *testing.T) { 182 L := NewState() 183 defer L.Close() 184 L.Push(LNumber(10)) 185 L.Push(LString("99.9")) 186 L.Push(L.NewTable()) 187 errorIfNotEqual(t, LNumber(10), L.ToNumber(1)) 188 errorIfNotEqual(t, LNumber(99.9), L.ToNumber(2)) 189 errorIfNotEqual(t, LNumber(0), L.ToNumber(3)) 190 } 191 192 func TestToString(t *testing.T) { 193 L := NewState() 194 defer L.Close() 195 L.Push(LNumber(10)) 196 L.Push(LString("99.9")) 197 L.Push(L.NewTable()) 198 errorIfNotEqual(t, "10", L.ToString(1)) 199 errorIfNotEqual(t, "99.9", L.ToString(2)) 200 errorIfNotEqual(t, "", L.ToString(3)) 201 } 202 203 func TestToTable(t *testing.T) { 204 L := NewState() 205 defer L.Close() 206 L.Push(LNumber(10)) 207 L.Push(LString("99.9")) 208 L.Push(L.NewTable()) 209 errorIfFalse(t, L.ToTable(1) == nil, "index 1 must be nil") 210 errorIfFalse(t, L.ToTable(2) == nil, "index 2 must be nil") 211 errorIfNotEqual(t, L.Get(3), L.ToTable(3)) 212 } 213 214 func TestToFunction(t *testing.T) { 215 L := NewState() 216 defer L.Close() 217 L.Push(LNumber(10)) 218 L.Push(LString("99.9")) 219 L.Push(L.NewFunction(func(L *LState) int { return 0 })) 220 errorIfFalse(t, L.ToFunction(1) == nil, "index 1 must be nil") 221 errorIfFalse(t, L.ToFunction(2) == nil, "index 2 must be nil") 222 errorIfNotEqual(t, L.Get(3), L.ToFunction(3)) 223 } 224 225 func TestToUserData(t *testing.T) { 226 L := NewState() 227 defer L.Close() 228 L.Push(LNumber(10)) 229 L.Push(LString("99.9")) 230 L.Push(L.NewUserData()) 231 errorIfFalse(t, L.ToUserData(1) == nil, "index 1 must be nil") 232 errorIfFalse(t, L.ToUserData(2) == nil, "index 2 must be nil") 233 errorIfNotEqual(t, L.Get(3), L.ToUserData(3)) 234 } 235 236 func TestToChannel(t *testing.T) { 237 L := NewState() 238 defer L.Close() 239 L.Push(LNumber(10)) 240 L.Push(LString("99.9")) 241 var ch chan LValue 242 L.Push(LChannel(ch)) 243 errorIfFalse(t, L.ToChannel(1) == nil, "index 1 must be nil") 244 errorIfFalse(t, L.ToChannel(2) == nil, "index 2 must be nil") 245 errorIfNotEqual(t, ch, L.ToChannel(3)) 246 } 247 248 func TestObjLen(t *testing.T) { 249 L := NewState() 250 defer L.Close() 251 errorIfNotEqual(t, 3, L.ObjLen(LString("abc"))) 252 tbl := L.NewTable() 253 tbl.Append(LTrue) 254 tbl.Append(LTrue) 255 errorIfNotEqual(t, 2, L.ObjLen(tbl)) 256 mt := L.NewTable() 257 L.SetField(mt, "__len", L.NewFunction(func(L *LState) int { 258 tbl := L.CheckTable(1) 259 L.Push(LNumber(tbl.Len() + 1)) 260 return 1 261 })) 262 L.SetMetatable(tbl, mt) 263 errorIfNotEqual(t, 3, L.ObjLen(tbl)) 264 errorIfNotEqual(t, 0, L.ObjLen(LNumber(10))) 265 } 266 267 func TestConcat(t *testing.T) { 268 L := NewState() 269 defer L.Close() 270 errorIfNotEqual(t, "a1c", L.Concat(LString("a"), LNumber(1), LString("c"))) 271 } 272 273 func TestPCall(t *testing.T) { 274 L := NewState() 275 defer L.Close() 276 L.Register("f1", func(L *LState) int { 277 panic("panic!") 278 return 0 279 }) 280 errorIfScriptNotFail(t, L, `f1()`, "panic!") 281 L.Push(L.GetGlobal("f1")) 282 err := L.PCall(0, 0, L.NewFunction(func(L *LState) int { 283 L.Push(LString("by handler")) 284 return 1 285 })) 286 errorIfFalse(t, strings.Contains(err.Error(), "by handler"), "") 287 288 err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { 289 L.RaiseError("error!") 290 return 1 291 })) 292 errorIfFalse(t, strings.Contains(err.Error(), "error!"), "") 293 294 err = L.PCall(0, 0, L.NewFunction(func(L *LState) int { 295 panic("panicc!") 296 return 1 297 })) 298 errorIfFalse(t, strings.Contains(err.Error(), "panicc!"), "") 299 } 300 301 func TestCoroutineApi1(t *testing.T) { 302 L := NewState() 303 defer L.Close() 304 co, _ := L.NewThread() 305 errorIfScriptFail(t, L, ` 306 function coro(v) 307 assert(v == 10) 308 local ret1, ret2 = coroutine.yield(1,2,3) 309 assert(ret1 == 11) 310 assert(ret2 == 12) 311 coroutine.yield(4) 312 return 5 313 end 314 `) 315 fn := L.GetGlobal("coro").(*LFunction) 316 st, err, values := L.Resume(co, fn, LNumber(10)) 317 errorIfNotEqual(t, ResumeYield, st) 318 errorIfNotNil(t, err) 319 errorIfNotEqual(t, 3, len(values)) 320 errorIfNotEqual(t, LNumber(1), values[0].(LNumber)) 321 errorIfNotEqual(t, LNumber(2), values[1].(LNumber)) 322 errorIfNotEqual(t, LNumber(3), values[2].(LNumber)) 323 324 st, err, values = L.Resume(co, fn, LNumber(11), LNumber(12)) 325 errorIfNotEqual(t, ResumeYield, st) 326 errorIfNotNil(t, err) 327 errorIfNotEqual(t, 1, len(values)) 328 errorIfNotEqual(t, LNumber(4), values[0].(LNumber)) 329 330 st, err, values = L.Resume(co, fn) 331 errorIfNotEqual(t, ResumeOK, st) 332 errorIfNotNil(t, err) 333 errorIfNotEqual(t, 1, len(values)) 334 errorIfNotEqual(t, LNumber(5), values[0].(LNumber)) 335 336 L.Register("myyield", func(L *LState) int { 337 return L.Yield(L.ToNumber(1)) 338 }) 339 errorIfScriptFail(t, L, ` 340 function coro_error() 341 coroutine.yield(1,2,3) 342 myyield(4) 343 assert(false, "--failed--") 344 end 345 `) 346 fn = L.GetGlobal("coro_error").(*LFunction) 347 co, _ = L.NewThread() 348 st, err, values = L.Resume(co, fn) 349 errorIfNotEqual(t, ResumeYield, st) 350 errorIfNotNil(t, err) 351 errorIfNotEqual(t, 3, len(values)) 352 errorIfNotEqual(t, LNumber(1), values[0].(LNumber)) 353 errorIfNotEqual(t, LNumber(2), values[1].(LNumber)) 354 errorIfNotEqual(t, LNumber(3), values[2].(LNumber)) 355 356 st, err, values = L.Resume(co, fn) 357 errorIfNotEqual(t, ResumeYield, st) 358 errorIfNotNil(t, err) 359 errorIfNotEqual(t, 1, len(values)) 360 errorIfNotEqual(t, LNumber(4), values[0].(LNumber)) 361 362 st, err, values = L.Resume(co, fn) 363 errorIfNotEqual(t, ResumeError, st) 364 errorIfNil(t, err) 365 errorIfFalse(t, strings.Contains(err.Error(), "--failed--"), "error message must be '--failed--'") 366 st, err, values = L.Resume(co, fn) 367 errorIfNotEqual(t, ResumeError, st) 368 errorIfNil(t, err) 369 errorIfFalse(t, strings.Contains(err.Error(), "can not resume a dead thread"), "can not resume a dead thread") 370 371 } 372 373 func TestContextTimeout(t *testing.T) { 374 L := NewState() 375 defer L.Close() 376 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 377 defer cancel() 378 L.SetContext(ctx) 379 errorIfNotEqual(t, ctx, L.Context()) 380 err := L.DoString(` 381 local clock = os.clock 382 function sleep(n) -- seconds 383 local t0 = clock() 384 while clock() - t0 <= n do end 385 end 386 sleep(3) 387 `) 388 errorIfNil(t, err) 389 errorIfFalse(t, strings.Contains(err.Error(), "context deadline exceeded"), "execution must be canceled") 390 391 oldctx := L.RemoveContext() 392 errorIfNotEqual(t, ctx, oldctx) 393 errorIfNotNil(t, L.ctx) 394 } 395 396 func TestContextCancel(t *testing.T) { 397 L := NewState() 398 defer L.Close() 399 ctx, cancel := context.WithCancel(context.Background()) 400 errch := make(chan error, 1) 401 L.SetContext(ctx) 402 go func() { 403 errch <- L.DoString(` 404 local clock = os.clock 405 function sleep(n) -- seconds 406 local t0 = clock() 407 while clock() - t0 <= n do end 408 end 409 sleep(3) 410 `) 411 }() 412 time.Sleep(1 * time.Second) 413 cancel() 414 err := <-errch 415 errorIfNil(t, err) 416 errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "execution must be canceled") 417 } 418 419 func TestContextWithCroutine(t *testing.T) { 420 L := NewState() 421 defer L.Close() 422 ctx, cancel := context.WithCancel(context.Background()) 423 L.SetContext(ctx) 424 defer cancel() 425 L.DoString(` 426 function coro() 427 local i = 0 428 while true do 429 coroutine.yield(i) 430 i = i+1 431 end 432 return i 433 end 434 `) 435 co, cocancel := L.NewThread() 436 defer cocancel() 437 fn := L.GetGlobal("coro").(*LFunction) 438 _, err, values := L.Resume(co, fn) 439 errorIfNotNil(t, err) 440 errorIfNotEqual(t, LNumber(0), values[0]) 441 // cancel the parent context 442 cancel() 443 _, err, values = L.Resume(co, fn) 444 errorIfNil(t, err) 445 errorIfFalse(t, strings.Contains(err.Error(), "context canceled"), "coroutine execution must be canceled when the parent context is canceled") 446 447 } 448 449 func TestPCallAfterFail(t *testing.T) { 450 L := NewState() 451 defer L.Close() 452 errFn := L.NewFunction(func(L *LState) int { 453 L.RaiseError("error!") 454 return 0 455 }) 456 changeError := L.NewFunction(func(L *LState) int { 457 L.Push(errFn) 458 err := L.PCall(0, 0, nil) 459 if err != nil { 460 L.RaiseError("A New Error") 461 } 462 return 0 463 }) 464 L.Push(changeError) 465 err := L.PCall(0, 0, nil) 466 errorIfFalse(t, strings.Contains(err.Error(), "A New Error"), "error not propogated correctly") 467 } 468 469 func TestRegistryFixedOverflow(t *testing.T) { 470 state := NewState() 471 defer state.Close() 472 reg := state.reg 473 expectedPanic := false 474 // should be non auto grow by default 475 errorIfFalse(t, reg.maxSize == 0, "state should default to non-auto growing implementation") 476 // fill the stack and check we get a panic 477 test := LString("test") 478 for i := 0; i < len(reg.array); i++ { 479 reg.Push(test) 480 } 481 defer func() { 482 rcv := recover() 483 if rcv != nil { 484 if expectedPanic { 485 errorIfFalse(t, rcv.(error).Error() != "registry overflow", "expected registry overflow exception, got "+rcv.(error).Error()) 486 } else { 487 t.Errorf("did not expect registry overflow") 488 } 489 } else if expectedPanic { 490 t.Errorf("expected registry overflow exception, but didn't get panic") 491 } 492 }() 493 expectedPanic = true 494 reg.Push(test) 495 } 496 497 func TestRegistryAutoGrow(t *testing.T) { 498 state := NewState(Options{RegistryMaxSize: 300, RegistrySize: 200, RegistryGrowStep: 25}) 499 defer state.Close() 500 expectedPanic := false 501 defer func() { 502 rcv := recover() 503 if rcv != nil { 504 if expectedPanic { 505 errorIfFalse(t, rcv.(error).Error() != "registry overflow", "expected registry overflow exception, got "+rcv.(error).Error()) 506 } else { 507 t.Errorf("did not expect registry overflow") 508 } 509 } else if expectedPanic { 510 t.Errorf("expected registry overflow exception, but didn't get panic") 511 } 512 }() 513 reg := state.reg 514 test := LString("test") 515 for i := 0; i < 300; i++ { 516 reg.Push(test) 517 } 518 expectedPanic = true 519 reg.Push(test) 520 } 521 522 func BenchmarkCallFrameStackPushPopAutoGrow(t *testing.B) { 523 stack := newAutoGrowingCallFrameStack(256) 524 525 t.ResetTimer() 526 527 const Iterations = 256 528 for j := 0; j < t.N; j++ { 529 for i := 0; i < Iterations; i++ { 530 stack.Push(callFrame{}) 531 } 532 for i := 0; i < Iterations; i++ { 533 stack.Pop() 534 } 535 } 536 } 537 538 func BenchmarkCallFrameStackPushPopFixed(t *testing.B) { 539 stack := newFixedCallFrameStack(256) 540 541 t.ResetTimer() 542 543 const Iterations = 256 544 for j := 0; j < t.N; j++ { 545 for i := 0; i < Iterations; i++ { 546 stack.Push(callFrame{}) 547 } 548 for i := 0; i < Iterations; i++ { 549 stack.Pop() 550 } 551 } 552 } 553 554 // this test will intentionally not incur stack growth in order to bench the performance when no allocations happen 555 func BenchmarkCallFrameStackPushPopShallowAutoGrow(t *testing.B) { 556 stack := newAutoGrowingCallFrameStack(256) 557 558 t.ResetTimer() 559 560 const Iterations = 8 561 for j := 0; j < t.N; j++ { 562 for i := 0; i < Iterations; i++ { 563 stack.Push(callFrame{}) 564 } 565 for i := 0; i < Iterations; i++ { 566 stack.Pop() 567 } 568 } 569 } 570 571 func BenchmarkCallFrameStackPushPopShallowFixed(t *testing.B) { 572 stack := newFixedCallFrameStack(256) 573 574 t.ResetTimer() 575 576 const Iterations = 8 577 for j := 0; j < t.N; j++ { 578 for i := 0; i < Iterations; i++ { 579 stack.Push(callFrame{}) 580 } 581 for i := 0; i < Iterations; i++ { 582 stack.Pop() 583 } 584 } 585 } 586 587 func BenchmarkCallFrameStackPushPopFixedNoInterface(t *testing.B) { 588 stack := newFixedCallFrameStack(256).(*fixedCallFrameStack) 589 590 t.ResetTimer() 591 592 const Iterations = 256 593 for j := 0; j < t.N; j++ { 594 for i := 0; i < Iterations; i++ { 595 stack.Push(callFrame{}) 596 } 597 for i := 0; i < Iterations; i++ { 598 stack.Pop() 599 } 600 } 601 } 602 603 func BenchmarkCallFrameStackUnwindAutoGrow(t *testing.B) { 604 stack := newAutoGrowingCallFrameStack(256) 605 606 t.ResetTimer() 607 608 const Iterations = 256 609 for j := 0; j < t.N; j++ { 610 for i := 0; i < Iterations; i++ { 611 stack.Push(callFrame{}) 612 } 613 stack.SetSp(0) 614 } 615 } 616 617 func BenchmarkCallFrameStackUnwindFixed(t *testing.B) { 618 stack := newFixedCallFrameStack(256) 619 620 t.ResetTimer() 621 622 const Iterations = 256 623 for j := 0; j < t.N; j++ { 624 for i := 0; i < Iterations; i++ { 625 stack.Push(callFrame{}) 626 } 627 stack.SetSp(0) 628 } 629 } 630 631 func BenchmarkCallFrameStackUnwindFixedNoInterface(t *testing.B) { 632 stack := newFixedCallFrameStack(256).(*fixedCallFrameStack) 633 634 t.ResetTimer() 635 636 const Iterations = 256 637 for j := 0; j < t.N; j++ { 638 for i := 0; i < Iterations; i++ { 639 stack.Push(callFrame{}) 640 } 641 stack.SetSp(0) 642 } 643 } 644 645 type registryTestHandler int 646 647 func (registryTestHandler) registryOverflow() { 648 panic("registry overflow") 649 } 650 651 // test pushing and popping from the registry 652 func BenchmarkRegistryPushPopAutoGrow(t *testing.B) { 653 al := newAllocator(32) 654 sz := 256 * 20 655 reg := newRegistry(registryTestHandler(0), sz/2, 64, sz, al) 656 value := LString("test") 657 658 t.ResetTimer() 659 660 for j := 0; j < t.N; j++ { 661 for i := 0; i < sz; i++ { 662 reg.Push(value) 663 } 664 for i := 0; i < sz; i++ { 665 reg.Pop() 666 } 667 } 668 } 669 670 func BenchmarkRegistryPushPopFixed(t *testing.B) { 671 al := newAllocator(32) 672 sz := 256 * 20 673 reg := newRegistry(registryTestHandler(0), sz, 0, sz, al) 674 value := LString("test") 675 676 t.ResetTimer() 677 678 for j := 0; j < t.N; j++ { 679 for i := 0; i < sz; i++ { 680 reg.Push(value) 681 } 682 for i := 0; i < sz; i++ { 683 reg.Pop() 684 } 685 } 686 } 687 688 func BenchmarkRegistrySetTop(t *testing.B) { 689 al := newAllocator(32) 690 sz := 256 * 20 691 reg := newRegistry(registryTestHandler(0), sz, 32, sz*2, al) 692 693 t.ResetTimer() 694 695 for j := 0; j < t.N; j++ { 696 reg.SetTop(sz) 697 reg.SetTop(0) 698 } 699 }