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  }