github.com/timandy/routine@v1.1.4-0.20240507073150-e4a3e1fe2ba5/future_task_test.go (about) 1 package routine 2 3 import ( 4 "strings" 5 "sync" 6 "sync/atomic" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 ) 12 13 func TestFutureTask_IsDone(t *testing.T) { 14 task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 15 assert.False(t, task.IsDone()) 16 task.Complete(nil) 17 assert.True(t, task.IsDone()) 18 // 19 task2 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 20 assert.False(t, task2.IsDone()) 21 task2.Cancel() 22 assert.True(t, task2.IsDone()) 23 // 24 task3 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 25 assert.False(t, task3.IsDone()) 26 task3.Fail(nil) 27 assert.True(t, task3.IsDone()) 28 // 29 task4 := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 30 assert.False(t, task4.IsDone()) 31 task4.(*futureTask[*int]).state = taskStateRunning 32 assert.False(t, task4.IsDone()) 33 } 34 35 func TestFutureTask_IsCanceled(t *testing.T) { 36 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 37 assert.False(t, task.IsCanceled()) 38 task.Cancel() 39 assert.True(t, task.IsCanceled()) 40 } 41 42 func TestFutureTask_IsFailed(t *testing.T) { 43 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 44 assert.False(t, task.IsFailed()) 45 task.Fail(nil) 46 assert.True(t, task.IsFailed()) 47 } 48 49 func TestFutureTask_Complete_AfterCancel(t *testing.T) { 50 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 51 go func() { 52 task.Cancel() 53 }() 54 assert.Panics(t, func() { 55 task.Get() 56 }) 57 assert.True(t, task.IsCanceled()) 58 // 59 task.Complete(2) 60 assert.Panics(t, func() { 61 task.Get() 62 }) 63 assert.True(t, task.IsCanceled()) 64 } 65 66 func TestFutureTask_Complete_AfterComplete(t *testing.T) { 67 task := NewFutureTask(func(task FutureTask[int]) int { return 1 }) 68 task.Run() 69 assert.Equal(t, 1, task.Get()) 70 task.Complete(2) 71 assert.Equal(t, 1, task.Get()) 72 // 73 run := false 74 task2 := NewFutureTask(func(task FutureTask[int]) int { 75 run = true 76 return 1 77 }) 78 task2.Complete(2) 79 task2.Run() 80 assert.Equal(t, 2, task2.Get()) 81 assert.False(t, run) 82 } 83 84 func TestFutureTask_Complete_Common(t *testing.T) { 85 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 86 go func() { 87 task.Complete(1) 88 }() 89 assert.Equal(t, 1, task.Get()) 90 //complete again won't change the result 91 go func() { 92 task.Complete(2) 93 }() 94 assert.Equal(t, 1, task.Get()) 95 } 96 97 func TestFutureTask_Cancel_AfterComplete(t *testing.T) { 98 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 99 go func() { 100 task.Complete(1) 101 }() 102 assert.Equal(t, 1, task.Get()) 103 task.Cancel() 104 assert.False(t, task.IsCanceled()) 105 assert.Equal(t, 1, task.Get()) 106 } 107 108 func TestFutureTask_Cancel_Common(t *testing.T) { 109 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 110 go func() { 111 task.Cancel() 112 }() 113 assert.Panics(t, func() { 114 task.Get() 115 }) 116 assert.True(t, task.IsCanceled()) 117 assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) 118 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 119 } 120 121 func TestFutureTask_Cancel_RuntimeError(t *testing.T) { 122 task3 := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 123 go func() { 124 task3.Cancel() 125 }() 126 assert.Panics(t, func() { 127 task3.Get() 128 }) 129 assert.True(t, task3.IsCanceled()) 130 assert.Equal(t, "Task was canceled.", task3.(*futureTask[int]).error.Message()) 131 assert.Nil(t, task3.(*futureTask[int]).error.Cause()) 132 } 133 134 func TestFutureTask_Fail_AfterComplete(t *testing.T) { 135 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 136 go func() { 137 task.Complete(1) 138 }() 139 assert.Equal(t, 1, task.Get()) 140 task.Fail(1) 141 assert.False(t, task.IsFailed()) 142 assert.Equal(t, 1, task.Get()) 143 } 144 145 func TestFutureTask_Fail_Common(t *testing.T) { 146 defer func() { 147 if cause := recover(); cause != nil { 148 err := cause.(RuntimeError) 149 assert.NotNil(t, err) 150 assert.Equal(t, "1", err.Message()) 151 lines := strings.Split(err.Error(), newLine) 152 // 153 line := lines[0] 154 assert.Equal(t, "RuntimeError: 1", line) 155 // 156 line = lines[1] 157 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_Common.")) 158 assert.True(t, strings.HasSuffix(line, "future_task_test.go:169")) 159 } 160 }() 161 // 162 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 163 go func() { 164 defer func() { 165 if cause := recover(); cause != nil { 166 task.Fail(cause) 167 } 168 }() 169 panic(1) 170 }() 171 task.Get() 172 assert.Fail(t, "should not be here") 173 } 174 175 func TestFutureTask_Fail_RuntimeError(t *testing.T) { 176 defer func() { 177 if cause := recover(); cause != nil { 178 err := cause.(RuntimeError) 179 assert.NotNil(t, err) 180 assert.Equal(t, "1", err.Message()) 181 lines := strings.Split(err.Error(), newLine) 182 assert.Equal(t, 4, len(lines)) 183 // 184 line := lines[0] 185 assert.Equal(t, "RuntimeError: 1", line) 186 // 187 line = lines[1] 188 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Fail_RuntimeError.")) 189 assert.True(t, strings.HasSuffix(line, "future_task_test.go:207")) 190 // 191 line = lines[2] 192 assert.Equal(t, " --- End of error stack trace ---", line) 193 // 194 line = lines[3] 195 assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Fail_RuntimeError()")) 196 assert.True(t, strings.HasSuffix(line, "future_task_test.go:201")) 197 } 198 }() 199 // 200 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 201 go func() { 202 defer func() { 203 if cause := recover(); cause != nil { 204 task.Fail(NewRuntimeError(cause)) 205 } 206 }() 207 panic(1) 208 }() 209 task.Get() 210 assert.Fail(t, "should not be here") 211 } 212 213 func TestFutureTask_Get_Nil(t *testing.T) { 214 run := false 215 task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 216 go func() { 217 time.Sleep(100 * time.Millisecond) 218 run = true 219 task.Complete(nil) 220 }() 221 assert.Nil(t, task.Get()) 222 assert.True(t, run) 223 } 224 225 func TestFutureTask_Get_Common(t *testing.T) { 226 run := false 227 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 228 go func() { 229 time.Sleep(100 * time.Millisecond) 230 run = true 231 task.Complete(1) 232 }() 233 assert.Equal(t, 1, task.Get()) 234 assert.True(t, run) 235 } 236 237 func TestFutureTask_GetWithTimeout_Complete(t *testing.T) { 238 wg := &sync.WaitGroup{} 239 wg.Add(1) 240 // 241 run := false 242 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 243 go func() { 244 defer wg.Done() 245 // 246 if task.IsCanceled() { 247 return 248 } 249 run = true 250 task.Complete(1) 251 }() 252 assert.Equal(t, 1, task.GetWithTimeout(100*time.Millisecond)) 253 assert.True(t, run) 254 // 255 wg.Wait() 256 } 257 258 func TestFutureTask_GetWithTimeout_Fail(t *testing.T) { 259 wg := &sync.WaitGroup{} 260 wg.Add(1) 261 // 262 run := false 263 task := NewFutureTask[int](func(task FutureTask[int]) int { return 0 }) 264 go func() { 265 defer wg.Done() 266 // 267 if task.IsCanceled() { 268 return 269 } 270 run = true 271 task.Fail(1) 272 }() 273 assert.Panics(t, func() { 274 task.GetWithTimeout(100 * time.Millisecond) 275 }) 276 assert.True(t, run) 277 // 278 assert.True(t, task.IsFailed()) 279 assert.Equal(t, "1", task.(*futureTask[int]).error.Message()) 280 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 281 // 282 wg.Wait() 283 } 284 285 func TestFutureTask_GetWithTimeout_Timeout(t *testing.T) { 286 wg := &sync.WaitGroup{} 287 wg.Add(1) 288 // 289 run := false 290 task := NewFutureTask[*int](func(task FutureTask[*int]) *int { return nil }) 291 go func() { 292 defer wg.Done() 293 // 294 time.Sleep(100 * time.Millisecond) 295 if task.IsCanceled() { 296 return 297 } 298 run = true 299 task.Complete(nil) 300 }() 301 assert.Panics(t, func() { 302 task.GetWithTimeout(1 * time.Millisecond) 303 }) 304 assert.False(t, run) 305 // 306 assert.True(t, task.IsCanceled()) 307 assert.Equal(t, "Task execution timeout after 1ms.", task.(*futureTask[*int]).error.Message()) 308 assert.Nil(t, task.(*futureTask[*int]).error.Cause()) 309 // 310 wg.Wait() 311 } 312 313 func TestFutureTask_Run_AfterCancel(t *testing.T) { 314 run := false 315 task := NewFutureTask(func(task FutureTask[*int]) *int { 316 run = true 317 return nil 318 }) 319 task.Cancel() 320 task.Run() 321 assert.Panics(t, func() { 322 task.Get() 323 }) 324 assert.True(t, task.IsCanceled()) 325 assert.False(t, run) 326 } 327 328 func TestFutureTask_Run_AfterFail(t *testing.T) { 329 run := false 330 task := NewFutureTask(func(task FutureTask[*int]) *int { 331 run = true 332 return nil 333 }) 334 task.Fail("failed.") 335 task.Run() 336 assert.Panics(t, func() { 337 task.Get() 338 }) 339 assert.True(t, task.IsFailed()) 340 assert.False(t, run) 341 } 342 343 func TestFutureTask_Run_AfterComplete(t *testing.T) { 344 run := false 345 task := NewFutureTask(func(task FutureTask[int]) int { 346 run = true 347 return 0 348 }) 349 task.Complete(1) 350 task.Run() 351 assert.Equal(t, 1, task.Get()) 352 assert.True(t, task.IsDone()) 353 assert.False(t, run) 354 } 355 356 func TestFutureTask_Run_AfterRun(t *testing.T) { 357 var run int32 = 0 358 wg := &sync.WaitGroup{} 359 wg.Add(1) 360 wg2 := &sync.WaitGroup{} 361 wg2.Add(1) 362 task := NewFutureTask(func(task FutureTask[int]) int { 363 atomic.AddInt32(&run, 1) 364 wg.Done() 365 wg2.Wait() 366 return 1 367 }) 368 go task.Run() 369 wg.Wait() 370 task.Run() 371 wg2.Done() 372 assert.Equal(t, 1, task.Get()) 373 assert.True(t, task.IsDone()) 374 assert.Equal(t, int32(1), atomic.LoadInt32(&run)) 375 } 376 377 func TestFutureTask_Run_Normal(t *testing.T) { 378 run := false 379 wg := &sync.WaitGroup{} 380 wg.Add(1) 381 task := NewFutureTask(func(task FutureTask[int]) int { 382 run = true 383 return 1 384 }) 385 go task.Run() 386 assert.Equal(t, 1, task.Get()) 387 assert.True(t, task.IsDone()) 388 assert.True(t, run) 389 } 390 391 func TestFutureTask_Run_Error(t *testing.T) { 392 run := false 393 wg := &sync.WaitGroup{} 394 wg.Add(1) 395 task := NewFutureTask(func(task FutureTask[int]) int { 396 run = true 397 panic(1) 398 }) 399 go task.Run() 400 assert.Panics(t, func() { 401 task.Get() 402 }) 403 assert.True(t, task.IsFailed()) 404 assert.True(t, run) 405 // 406 defer func() { 407 cause := recover() 408 assert.NotNil(t, cause) 409 assert.Implements(t, (*RuntimeError)(nil), cause) 410 err := cause.(RuntimeError) 411 assert.Equal(t, "1", err.Message()) 412 lines := strings.Split(err.Error(), newLine) 413 assert.Equal(t, 5, len(lines)) 414 // 415 line := lines[0] 416 assert.Equal(t, "RuntimeError: 1", line) 417 // 418 line = lines[1] 419 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_Error.")) 420 assert.True(t, strings.HasSuffix(line, "future_task_test.go:397")) 421 // 422 line = lines[2] 423 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) 424 assert.True(t, strings.HasSuffix(line, "future_task.go:108")) 425 // 426 line = lines[3] 427 assert.Equal(t, " --- End of error stack trace ---", line) 428 // 429 line = lines[4] 430 assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_Error()")) 431 assert.True(t, strings.HasSuffix(line, "future_task_test.go:399")) 432 }() 433 task.Get() 434 assert.Fail(t, "should not be here") 435 } 436 437 func TestFutureTask_Run_RuntimeError(t *testing.T) { 438 run := false 439 wg := &sync.WaitGroup{} 440 wg.Add(1) 441 task := NewFutureTask(func(task FutureTask[int]) int { 442 run = true 443 err := NewRuntimeError(1) 444 panic(err) 445 }) 446 go task.Run() 447 assert.Panics(t, func() { 448 task.Get() 449 }) 450 assert.True(t, task.IsFailed()) 451 assert.True(t, run) 452 // 453 defer func() { 454 cause := recover() 455 assert.NotNil(t, cause) 456 assert.Implements(t, (*RuntimeError)(nil), cause) 457 err := cause.(RuntimeError) 458 assert.Equal(t, "1", err.Message()) 459 lines := strings.Split(err.Error(), newLine) 460 assert.Equal(t, 5, len(lines)) 461 // 462 line := lines[0] 463 assert.Equal(t, "RuntimeError: 1", line) 464 // 465 line = lines[1] 466 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.TestFutureTask_Run_RuntimeError.")) 467 assert.True(t, strings.HasSuffix(line, "future_task_test.go:443")) 468 // 469 line = lines[2] 470 assert.True(t, strings.HasPrefix(line, " at github.com/timandy/routine.(*futureTask[...]).Run()")) 471 assert.True(t, strings.HasSuffix(line, "future_task.go:108")) 472 // 473 line = lines[3] 474 assert.Equal(t, " --- End of error stack trace ---", line) 475 // 476 line = lines[4] 477 assert.True(t, strings.HasPrefix(line, " created by github.com/timandy/routine.TestFutureTask_Run_RuntimeError()")) 478 assert.True(t, strings.HasSuffix(line, "future_task_test.go:446")) 479 }() 480 task.Get() 481 assert.Fail(t, "should not be here") 482 } 483 484 func TestFutureTask_Routine_Complete(t *testing.T) { 485 wg := &sync.WaitGroup{} 486 wg.Add(1) 487 // 488 task := GoWaitResult(func(token CancelToken) int { 489 defer wg.Done() 490 // 491 if token.IsCanceled() { 492 panic("canceled") 493 } 494 time.Sleep(1 * time.Millisecond) 495 return 1 496 }) 497 assert.Equal(t, 1, task.GetWithTimeout(100*time.Millisecond)) 498 // 499 wg.Wait() 500 } 501 502 func TestFutureTask_Routine_Cancel(t *testing.T) { 503 wg := &sync.WaitGroup{} 504 wg.Add(1) 505 // 506 task := GoWaitResult(func(token CancelToken) int { 507 defer wg.Done() 508 // 509 token.Cancel() 510 return 1 511 }) 512 assert.Panics(t, func() { 513 task.GetWithTimeout(100 * time.Millisecond) 514 }) 515 assert.True(t, task.IsCanceled()) 516 assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) 517 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 518 // 519 wg.Wait() 520 } 521 522 func TestFutureTask_Routine_CancelInParent(t *testing.T) { 523 wg := &sync.WaitGroup{} 524 wg.Add(1) 525 wg2 := &sync.WaitGroup{} 526 wg2.Add(1) 527 // 528 finished := false 529 task := GoWaitResult(func(token CancelToken) int { 530 wg2.Done() 531 defer wg.Done() 532 // 533 for i := 0; i < 10; i++ { 534 time.Sleep(10 * time.Millisecond) 535 if token.IsCanceled() { 536 return 0 537 } 538 } 539 finished = true 540 return 1 541 }) 542 wg2.Wait() 543 task.Cancel() 544 // 545 wg.Wait() 546 // 547 assert.False(t, finished) 548 assert.True(t, task.IsCanceled()) 549 assert.Equal(t, "Task was canceled.", task.(*futureTask[int]).error.Message()) 550 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 551 } 552 553 func TestFutureTask_Routine_Fail(t *testing.T) { 554 wg := &sync.WaitGroup{} 555 wg.Add(1) 556 // 557 task := GoWaitResult(func(token CancelToken) int { 558 defer wg.Done() 559 // 560 if token.IsCanceled() { 561 return 1 562 } 563 panic("something error") 564 }) 565 assert.Panics(t, func() { 566 task.GetWithTimeout(100 * time.Millisecond) 567 }) 568 assert.True(t, task.IsFailed()) 569 assert.Equal(t, "something error", task.(*futureTask[int]).error.Message()) 570 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 571 // 572 wg.Wait() 573 } 574 575 func TestFutureTask_Routine_Timeout(t *testing.T) { 576 wg := &sync.WaitGroup{} 577 wg.Add(1) 578 // 579 task := GoWaitResult(func(token CancelToken) int { 580 defer wg.Done() 581 // 582 for i := 0; i < 10; i++ { 583 if token.IsCanceled() { 584 panic("canceled") 585 } 586 time.Sleep(10 * time.Millisecond) 587 } 588 return 1 589 }) 590 assert.Panics(t, func() { 591 task.GetWithTimeout(1 * time.Millisecond) 592 }) 593 assert.True(t, task.IsCanceled()) 594 assert.Equal(t, "Task execution timeout after 1ms.", task.(*futureTask[int]).error.Message()) 595 assert.Nil(t, task.(*futureTask[int]).error.Cause()) 596 // 597 wg.Wait() 598 } 599 600 func TestFutureTask_Routine_TimeoutThenComplete(t *testing.T) { 601 wg := &sync.WaitGroup{} 602 wg.Add(1) 603 // 604 task := GoWait(func(token CancelToken) { 605 defer wg.Done() 606 // 607 ft := token.(*futureTask[any]) 608 ft.result = 1 609 assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateCompleted)) 610 time.Sleep(50 * time.Millisecond) 611 ft.await.Done() 612 }) 613 assert.Equal(t, 1, task.GetWithTimeout(10*time.Millisecond)) 614 assert.Equal(t, 1, task.Get()) 615 // 616 wg.Wait() 617 } 618 619 func TestFutureTask_Routine_TimeoutThenCancel(t *testing.T) { 620 wg := &sync.WaitGroup{} 621 wg.Add(1) 622 // 623 task := GoWait(func(token CancelToken) { 624 defer wg.Done() 625 // 626 ft := token.(*futureTask[any]) 627 ft.error = NewRuntimeError("canceled.") 628 assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateCanceled)) 629 time.Sleep(50 * time.Millisecond) 630 ft.await.Done() 631 }) 632 assert.Panics(t, func() { 633 task.GetWithTimeout(10 * time.Millisecond) 634 }) 635 // 636 assert.True(t, task.IsCanceled()) 637 assert.Equal(t, "canceled.", task.(*futureTask[any]).error.Message()) 638 assert.Nil(t, task.(*futureTask[any]).error.Cause()) 639 assert.Panics(t, func() { 640 task.Get() 641 }) 642 // 643 wg.Wait() 644 } 645 646 func TestFutureTask_Routine_TimeoutThenFail(t *testing.T) { 647 wg := &sync.WaitGroup{} 648 wg.Add(1) 649 // 650 task := GoWait(func(token CancelToken) { 651 defer wg.Done() 652 // 653 ft := token.(*futureTask[any]) 654 ft.error = NewRuntimeError("failed.") 655 assert.True(t, atomic.CompareAndSwapInt32(&ft.state, taskStateRunning, taskStateFailed)) 656 time.Sleep(50 * time.Millisecond) 657 ft.await.Done() 658 }) 659 assert.Panics(t, func() { 660 task.GetWithTimeout(10 * time.Millisecond) 661 }) 662 // 663 assert.True(t, task.IsFailed()) 664 assert.Equal(t, "failed.", task.(*futureTask[any]).error.Message()) 665 assert.Nil(t, task.(*futureTask[any]).error.Cause()) 666 assert.Panics(t, func() { 667 task.Get() 668 }) 669 // 670 wg.Wait() 671 }