github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/async/async_test.go (about) 1 package async 2 3 import ( 4 "fmt" 5 "sync/atomic" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 11 "github.com/gnolang/gno/tm2/pkg/errors" 12 ) 13 14 func TestParallel(t *testing.T) { 15 t.Parallel() 16 17 // Create tasks. 18 counter := new(int32) 19 tasks := make([]Task, 100*1000) 20 for i := 0; i < len(tasks); i++ { 21 tasks[i] = func(i int) (res interface{}, err error, abort bool) { 22 atomic.AddInt32(counter, 1) 23 return -1 * i, nil, false 24 } 25 } 26 27 // Run in parallel. 28 trs, ok := Parallel(tasks...) 29 assert.True(t, ok) 30 31 // Verify. 32 assert.Equal(t, int(*counter), len(tasks), "Each task should have incremented the counter already") 33 var failedTasks int 34 for i := 0; i < len(tasks); i++ { 35 taskResult, ok := trs.LatestResult(i) 36 switch { 37 case !ok: 38 assert.Fail(t, "Task #%v did not complete.", i) 39 failedTasks++ 40 case taskResult.Error != nil: 41 assert.Fail(t, "Task should not have errored but got %v", taskResult.Error) 42 failedTasks++ 43 case !assert.Equal(t, -1*i, taskResult.Value.(int)): 44 assert.Fail(t, "Task should have returned %v but got %v", -1*i, taskResult.Value.(int)) 45 failedTasks++ 46 } 47 // else { 48 // Good! 49 // } 50 } 51 assert.Equal(t, failedTasks, 0, "No task should have failed") 52 assert.Nil(t, trs.FirstError(), "There should be no errors") 53 assert.Equal(t, 0, trs.FirstValue(), "First value should be 0") 54 } 55 56 func TestParallelAbort(t *testing.T) { 57 t.Parallel() 58 59 flow1 := make(chan struct{}, 1) 60 flow2 := make(chan struct{}, 1) 61 flow3 := make(chan struct{}, 1) // Cap must be > 0 to prevent blocking. 62 flow4 := make(chan struct{}, 1) 63 64 // Create tasks. 65 tasks := []Task{ 66 func(i int) (res interface{}, err error, abort bool) { 67 assert.Equal(t, i, 0) 68 flow1 <- struct{}{} 69 return 0, nil, false 70 }, 71 func(i int) (res interface{}, err error, abort bool) { 72 assert.Equal(t, i, 1) 73 flow2 <- <-flow1 74 return 1, errors.New("some error"), false 75 }, 76 func(i int) (res interface{}, err error, abort bool) { 77 assert.Equal(t, i, 2) 78 flow3 <- <-flow2 79 return 2, nil, true 80 }, 81 func(i int) (res interface{}, err error, abort bool) { 82 assert.Equal(t, i, 3) 83 <-flow4 84 return 3, nil, false 85 }, 86 } 87 88 // Run in parallel. 89 taskResultSet, ok := Parallel(tasks...) 90 assert.False(t, ok, "ok should be false since we aborted task #2.") 91 92 // Verify task #3. 93 // Initially taskResultSet.chz[3] sends nothing since flow4 didn't send. 94 waitTimeout(t, taskResultSet.chz[3], "Task #3") 95 96 // Now let the last task (#3) complete after abort. 97 flow4 <- <-flow3 98 99 // Wait until all tasks have returned or panic'd. 100 taskResultSet.Wait() 101 102 // Verify task #0, #1, #2. 103 checkResult(t, taskResultSet, 0, 0, nil, nil) 104 checkResult(t, taskResultSet, 1, 1, errors.New("some error"), nil) 105 checkResult(t, taskResultSet, 2, 2, nil, nil) 106 checkResult(t, taskResultSet, 3, 3, nil, nil) 107 } 108 109 func TestParallelRecover(t *testing.T) { 110 t.Parallel() 111 112 // Create tasks. 113 tasks := []Task{ 114 func(i int) (res interface{}, err error, abort bool) { 115 return 0, nil, false 116 }, 117 func(i int) (res interface{}, err error, abort bool) { 118 return 1, errors.New("some error"), false 119 }, 120 func(i int) (res interface{}, err error, abort bool) { 121 panic(2) 122 }, 123 } 124 125 // Run in parallel. 126 taskResultSet, ok := Parallel(tasks...) 127 assert.False(t, ok, "ok should be false since we panic'd in task #2.") 128 129 // Verify task #0, #1, #2. 130 checkResult(t, taskResultSet, 0, 0, nil, nil) 131 checkResult(t, taskResultSet, 1, 1, errors.New("some error"), nil) 132 checkResult(t, taskResultSet, 2, nil, nil, 2) 133 } 134 135 // Wait for result 136 func checkResult(t *testing.T, taskResultSet *TaskResultSet, index int, val interface{}, err error, pnk interface{}) { 137 t.Helper() 138 139 taskResult, ok := taskResultSet.LatestResult(index) 140 taskName := fmt.Sprintf("Task #%v", index) 141 assert.True(t, ok, "TaskResultCh unexpectedly closed for %v", taskName) 142 assert.Equal(t, val, taskResult.Value, taskName) 143 switch { 144 case err != nil: 145 assert.Equal(t, err, taskResult.Error, taskName) 146 case pnk != nil: 147 assert.Equal(t, pnk, taskResult.Error.(errors.Error).Data(), taskName) 148 default: 149 assert.Nil(t, taskResult.Error, taskName) 150 } 151 } 152 153 // Wait for timeout (no result) 154 func waitTimeout(t *testing.T, taskResultCh TaskResultCh, taskName string) { 155 t.Helper() 156 157 select { 158 case _, ok := <-taskResultCh: 159 if !ok { 160 assert.Fail(t, "TaskResultCh unexpectedly closed (%v)", taskName) 161 } else { 162 assert.Fail(t, "TaskResultCh unexpectedly returned for %v", taskName) 163 } 164 case <-time.After(200 * time.Millisecond): // TODO use deterministic time? 165 // Good! 166 } 167 }